diff --git a/inttest/proto_gpb/proto_gpb_rt.erl b/inttest/proto_gpb/proto_gpb_rt.erl index 2cc50528..785922a5 100644 --- a/inttest/proto_gpb/proto_gpb_rt.erl +++ b/inttest/proto_gpb/proto_gpb_rt.erl @@ -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, @@ -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"}, @@ -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. @@ -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). @@ -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) -> diff --git a/rebar.config b/rebar.config index d90e1f11..33d2aeaa 100644 --- a/rebar.config +++ b/rebar.config @@ -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\"))", diff --git a/src/rebar_proto_compiler.erl b/src/rebar_proto_compiler.erl index bed9ced2..2d3eb2be 100644 --- a/src/rebar_proto_compiler.erl +++ b/src/rebar_proto_compiler.erl @@ -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 -> @@ -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; diff --git a/src/rebar_proto_gpb_compiler.erl b/src/rebar_proto_gpb_compiler.erl index 32a1f6d2..37f901ca 100644 --- a/src/rebar_proto_gpb_compiler.erl +++ b/src/rebar_proto_gpb_compiler.erl @@ -47,9 +47,12 @@ 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 -> @@ -57,14 +60,18 @@ proto_compile(Config, _AppFile, _ProtoFiles) -> ?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. %% =================================================================== @@ -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, ".").