Skip to content

Commit

Permalink
Recompile .proto files with gpb also with prefix/suffix
Browse files Browse the repository at this point in the history
For protocol buffer files, when there were gpb options to alter the
module name with prefix or suffix, recompilation was not properly
detected. This is now fixed. (Issue basho#384).

Use the rebar_base_compiler's ability to specify both source and
target file names, to be able to also support prefixes.

This also introduces a call to gpb_compile:format_error, so the xref
recipe needs to be updated to ignore it, to avoid false errors.
  • Loading branch information
tomas-abrahamsson committed Jan 24, 2015
1 parent 2e9706f commit 1ce7059
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 26 deletions.
50 changes: 50 additions & 0 deletions inttest/proto_gpb/proto_gpb_rt.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
run/1]).

-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
-include_lib("deps/retest/include/retest.hrl").

-define(MODULES,
[foo,
Expand All @@ -42,6 +44,13 @@
test4_gpb,
test5_gpb]).

-define(SOURCE_PROTO_FILES,
["test.proto",
"a/test2.proto",
"a/b/test3.proto",
"c/test4.proto",
"c/d/test5.proto"]).

files() ->
[
{copy, "../../rebar", "rebar"},
Expand All @@ -60,6 +69,17 @@ run(_Dir) ->
%% generating the test_gpb.hrl file, and also that it generated
%% the .hrl file was generated before foo was compiled.
ok = check_beams_generated(),

?DEBUG("Verifying recompilation~n", []),
TestErl = hd(generated_erl_files()),
TestProto = hd(source_proto_files()),
make_proto_newer_than_erl(TestProto, TestErl),
TestMTime1 = read_mtime(TestErl),
?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])),
TestMTime2 = read_mtime(TestErl),
?assert(TestMTime2 > TestMTime1),

?DEBUG("Verify cleanup~n", []),
?assertMatch({ok, _}, retest_sh:run("./rebar clean", [])),
ok = check_files_deleted(),
ok.
Expand All @@ -81,6 +101,12 @@ generated_erl_files() ->
generated_hrl_files() ->
add_dir("include", add_ext(?GENERATED_MODULES, ".hrl")).

generated_beam_files() ->
add_dir("ebin", add_ext(?GENERATED_MODULES, ".beam")).

source_proto_files() ->
add_dir("src", ?SOURCE_PROTO_FILES).

file_does_not_exist(F) ->
not filelib:is_regular(F).

Expand All @@ -90,6 +116,30 @@ add_ext(Modules, Ext) ->
add_dir(Dir, Files) ->
[filename:join(Dir, File) || File <- Files].

read_mtime(File) ->
{ok, #file_info{mtime=MTime}} = file:read_file_info(File),
MTime.


make_proto_newer_than_erl(Proto, Erl) ->
%% Do this by back-dating the erl file instead of touching the
%% proto file. Do this instead of sleeping for a second to get a
%% reliable test. Sleeping would have been needed sin ce the
%% #file_info{} (used by eg. filelib:last_modified) does not have
%% sub-second resolution (even though most file systems have).
{ok, #file_info{mtime=ProtoMTime}} = file:read_file_info(Proto),
{ok, ErlInfo} = file:read_file_info(Erl),
OlderMTime = update_seconds_to_datetime(ProtoMTime, -2),
OlderErlInfo = ErlInfo#file_info{mtime = OlderMTime},
ok = file:write_file_info(Erl, OlderErlInfo).

update_seconds_to_datetime(DT, ToAdd) ->
calendar:gregorian_seconds_to_datetime(
calendar:datetime_to_gregorian_seconds(DT) + ToAdd).

touch_file(File) ->
?assertMatch({ok, _}, retest_sh:run("touch " ++ File, [])).

check(Check, Files) ->
lists:foreach(
fun(F) ->
Expand Down
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- (\"neotoma\":\"file\"/\"2\")
- (\"protobuffs_compile\":\"scan_file\"/\"2\")
- (\"gpb_compile\":\"file\"/\"2\")
- (\"gpb_compile\":\"format_error\"/\"1\")
- (\"diameter_codegen\":\"from_dict\"/\"4\")
- (\"diameter_dict_util\":\"format_error\"/\"1\")
- (\"diameter_dict_util\":\"parse\"/\"2\"))",
Expand Down
4 changes: 2 additions & 2 deletions src/rebar_proto_compiler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
%% ===================================================================

compile(Config, AppFile) ->
case rebar_utils:find_files_by_ext("src", ".proto", true) of
case rebar_utils:find_files_by_ext("src", ".proto") of
[] ->
ok;
Protos ->
Expand All @@ -56,7 +56,7 @@ compile(Config, AppFile) ->

clean(Config, AppFile) ->
%% Get a list of generated .beam and .hrl files and then delete them
Protos = rebar_utils:find_files_by_ext("src", ".proto", true),
Protos = rebar_utils:find_files_by_ext("src", ".proto"),
case Protos of
[] ->
ok;
Expand Down
73 changes: 49 additions & 24 deletions src/rebar_proto_gpb_compiler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,31 @@ proto_compile(Config, _AppFile, _ProtoFiles) ->
%% since we have.proto files that need building
case gpb_is_present() of
true ->
GpbOpts = user_gpb_opts(Config),
Files = rebar_utils:find_files_by_ext("src", ".proto"),
Targets = [filename:join("src", target_filename(F, GpbOpts))
|| F <- Files],
rebar_base_compiler:run(Config, [],
"src", ".proto",
"src", ".erl",
lists:zip(Files, Targets),
fun compile_gpb/3,
[{check_last_mod, true}]);
false ->
?ERROR("The gpb library is not present in code path!\n", []),
?FAIL
end.

target_filename(ProtoFileName, GpbOpts) ->
ModulePrefix = proplists:get_value(module_name_prefix, GpbOpts, ""),
ModuleSuffix = proplists:get_value(module_name_suffix, GpbOpts, ""),
Base = filename:basename(ProtoFileName, ".proto"),
ModulePrefix ++ Base ++ ModuleSuffix ++ ".erl".

proto_clean(Config, _AppFile, ProtoFiles) ->
GpbOpts = gpb_opts(Config),
MPrefix = proplists:get_value(module_name_prefix, GpbOpts, ""),
MSuffix = proplists:get_value(module_name_suffix, GpbOpts, ""),
GpbOpts = user_gpb_opts(Config) ++ default_dest_opts(),
rebar_file_utils:delete_each(
[beam_relpath(MPrefix, F, MSuffix) || F <- ProtoFiles]
++ [erl_relpath(MPrefix, F, MSuffix) || F <- ProtoFiles]
++ [hrl_relpath(MPrefix, F, MSuffix) || F <- ProtoFiles]),
[beam_file(F, GpbOpts) || F <- ProtoFiles]
++ [erl_file(F, GpbOpts) || F <- ProtoFiles]
++ [hrl_file(F, GpbOpts) || F <- ProtoFiles]),
ok.

%% ===================================================================
Expand All @@ -82,37 +89,55 @@ proto_info(help, compile) ->
proto_info(help, clean) ->
?CONSOLE("", []).

gpb_opts(Config) ->
rebar_config:get_local(Config, gpb_opts, []).

gpb_is_present() ->
code:which(gpb) =/= non_existing.

user_gpb_opts(Config) ->
rebar_config:get_local(Config, gpb_opts, []).

default_dest_opts() ->
[{o_erl, "src"}, {o_hrl, "include"}].

compile_gpb(Source, _Target, Config) ->
SourceFullPath = filename:absname(Source),
DefaultDestOpts = [{o_erl, "src"}, {o_hrl, "include"}],
SelfIncludeOpt = [{i,filename:dirname(SourceFullPath)}],
GpbOpts = gpb_opts(Config) ++ DefaultDestOpts ++ SelfIncludeOpt,
GpbOpts = user_gpb_opts(Config) ++ default_dest_opts()
++ default_include_opts(SourceFullPath),
ok = filelib:ensure_dir(filename:join("ebin", "dummy")),
ok = filelib:ensure_dir(filename:join("include", "dummy")),
case gpb_compile:file(SourceFullPath, GpbOpts) of
ok ->
ok;
{error, _Reason} ->
?ERROR("Failed to compile ~s~n", [Source]),
{error, Reason} ->
ReasonStr = gpb_compile:format_error(Reason),
?ERROR("Failed to compile ~s: ~s~n", [SourceFullPath, ReasonStr]),
?FAIL
end.

beam_relpath(Prefix, Proto, Suffix) ->
proto_filename_to_relpath("ebin", Prefix, Proto, Suffix, ".beam").
default_include_opts(SourceFullPath) ->
[{i,filename:dirname(SourceFullPath)}].

beam_file(ProtoFile, GpbOpts) ->
proto_filename_to_path("ebin", ProtoFile, ".beam", GpbOpts).

erl_relpath(Prefix, Proto, Suffix) ->
proto_filename_to_relpath("src", Prefix, Proto, Suffix, ".erl").
erl_file(ProtoFile, GpbOpts) ->
ErlOutDir = get_erl_outdir(GpbOpts),
proto_filename_to_path(ErlOutDir, ProtoFile, ".erl", GpbOpts).

hrl_relpath(Prefix, Proto, Suffix) ->
proto_filename_to_relpath("include", Prefix, Proto, Suffix, ".hrl").
hrl_file(ProtoFile, GpbOpts) ->
HrlOutDir = get_hrl_outdir(GpbOpts),
proto_filename_to_path(HrlOutDir, ProtoFile, ".hrl", GpbOpts).

proto_filename_to_relpath(Dir, Prefix, Proto, Suffix, NewExt) ->
BaseNoExt = filename:basename(Proto, ".proto"),
proto_filename_to_path(Dir, ProtoFile, NewExt, GpbOpts) ->
BaseNoExt = filename:basename(ProtoFile, ".proto"),
Prefix = proplists:get_value(module_name_prefix, GpbOpts, ""),
Suffix = proplists:get_value(module_name_suffix, GpbOpts, ""),
filename:join([Dir, Prefix ++ BaseNoExt ++ Suffix ++ NewExt]).

get_erl_outdir(Opts) ->
proplists:get_value(o_erl, Opts, get_outdir(Opts)).

get_hrl_outdir(Opts) ->
proplists:get_value(o_hrl, Opts, get_outdir(Opts)).

get_outdir(Opts) ->
proplists:get_value(o, Opts, ".").

0 comments on commit 1ce7059

Please sign in to comment.