diff options
-rw-r--r-- | appveyor.yml | 20 | ||||
-rw-r--r-- | priv/templates/umbrella.template | 15 | ||||
-rw-r--r-- | src/rebar3.erl | 4 | ||||
-rw-r--r-- | src/rebar_opts.erl | 36 | ||||
-rw-r--r-- | src/rebar_prv_cover.erl | 44 | ||||
-rw-r--r-- | src/rebar_prv_escriptize.erl | 2 | ||||
-rw-r--r-- | test/rebar_profiles_SUITE.erl | 33 | ||||
-rw-r--r-- | test/rebar_release_SUITE.erl | 40 |
8 files changed, 155 insertions, 39 deletions
diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..fa44ffa --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,20 @@ +version: "{build}" +branches: + only: + - master +environment: + matrix: + - erlang_vsn: 19.2 + - erlang_vsn: 18.3 + - erlang_vsn: 17.5 +install: +- ps: choco install erlang --version $env:erlang_vsn +build_script: +- ps: cmd.exe /c 'bootstrap.bat' +test_script: +- ps: cmd.exe /c 'rebar3.cmd ct' +notifications: +- provider: GitHubPullRequest + on_build_success: true + on_build_failure: true + on_build_status_changed: false diff --git a/priv/templates/umbrella.template b/priv/templates/umbrella.template new file mode 100644 index 0000000..a2855d3 --- /dev/null +++ b/priv/templates/umbrella.template @@ -0,0 +1,15 @@ +{description, "OTP structure for executable programs (alias of 'release' template)"}. +{variables, [ + {name, "myapp", "Name of the OTP release. An app with this name will also be created."}, + {desc, "An OTP application", "Short description of the release's main app's purpose"} +]}. +{template, "app.erl", "{{name}}/{{apps_dir}}/{{name}}/src/{{name}}_app.erl"}. +{template, "sup.erl", "{{name}}/{{apps_dir}}/{{name}}/src/{{name}}_sup.erl"}. +{template, "otp_app.app.src", "{{name}}/{{apps_dir}}/{{name}}/src/{{name}}.app.src"}. +{template, "relx_rebar.config", "{{name}}/rebar.config"}. +{template, "sys.config", "{{name}}/config/sys.config"}. +{template, "vm.args", "{{name}}/config/vm.args"}. +{template, "gitignore", "{{name}}/.gitignore"}. +{template, "LICENSE", "{{name}}/LICENSE"}. +{template, "README.md", "{{name}}/README.md"}. + diff --git a/src/rebar3.erl b/src/rebar3.erl index eec8968..2b24bca 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -396,8 +396,8 @@ safe_define_test_macro(Opts) -> %% defining a compile macro twice results in an exception so %% make sure 'TEST' is only defined once case test_defined(Opts) of - true -> []; - false -> [{d, 'TEST'}] + true -> Opts; + false -> [{d, 'TEST'}|Opts] end. test_defined([{d, 'TEST'}|_]) -> true; diff --git a/src/rebar_opts.erl b/src/rebar_opts.erl index 589dbb8..b7156b2 100644 --- a/src/rebar_opts.erl +++ b/src/rebar_opts.erl @@ -117,7 +117,12 @@ merge_opt(plugins, NewValue, _OldValue) -> merge_opt({plugins, _}, NewValue, _OldValue) -> NewValue; merge_opt(profiles, NewValue, OldValue) -> - dict:to_list(merge_opts(dict:from_list(NewValue), dict:from_list(OldValue))); + %% Merge up sparse pairs of {Profile, Opts} into a joined up + %% {Profile, OptsNew, OptsOld} list. + ToMerge = normalise_profile_pairs(lists:sort(NewValue), + lists:sort(OldValue)), + [{K,dict:to_list(merge_opts(dict:from_list(New), dict:from_list(Old)))} + || {K,New,Old} <- ToMerge]; merge_opt(erl_first_files, Value, Value) -> Value; merge_opt(erl_first_files, NewValue, OldValue) -> @@ -127,7 +132,11 @@ merge_opt(mib_first_files, Value, Value) -> merge_opt(mib_first_files, NewValue, OldValue) -> OldValue ++ NewValue; merge_opt(relx, NewValue, OldValue) -> - rebar_utils:tup_umerge(OldValue, NewValue); + Partition = fun(C) -> is_tuple(C) andalso element(1, C) =:= overlay end, + {NewOverlays, NewOther} = lists:partition(Partition, NewValue), + {OldOverlays, OldOther} = lists:partition(Partition, OldValue), + rebar_utils:tup_umerge(NewOverlays, OldOverlays) + ++ rebar_utils:tup_umerge(OldOther, NewOther); merge_opt(Key, NewValue, OldValue) when Key == erl_opts; Key == eunit_compile_opts; Key == ct_compile_opts -> merge_erl_opts(lists:reverse(OldValue), NewValue); @@ -190,3 +199,26 @@ filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) -> end; filter_defines([Opt | Rest], Acc) -> filter_defines(Rest, [Opt | Acc]). + +%% @private takes two lists of profile tuples and merges them +%% into one list of 3-tuples containing the values of either +%% profiles. +%% Any missing profile in one of the keys is replaced by an +%% empty one. +-spec normalise_profile_pairs([Profile], [Profile]) -> [Pair] when + Profile :: {Name, Opts}, + Pair :: {Name, Opts, Opts}, + Name :: atom(), + Opts :: [term()]. +normalise_profile_pairs([], []) -> + []; +normalise_profile_pairs([{P,V}|Ps], []) -> + [{P,V,[]} | normalise_profile_pairs(Ps, [])]; +normalise_profile_pairs([], [{P,V}|Ps]) -> + [{P,[],V} | normalise_profile_pairs([], Ps)]; +normalise_profile_pairs([{P,VA}|PAs], [{P,VB}|PBs]) -> + [{P,VA,VB} | normalise_profile_pairs(PAs, PBs)]; +normalise_profile_pairs([{PA,VA}|PAs], [{PB,VB}|PBs]) when PA < PB -> + [{PA,VA,[]} | normalise_profile_pairs(PAs, [{PB, VB}|PBs])]; +normalise_profile_pairs([{PA,VA}|PAs], [{PB,VB}|PBs]) when PA > PB -> + [{PB,[],VB} | normalise_profile_pairs([{PA,VA}|PAs], PBs)]. diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index fca7c40..c890452 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -196,10 +196,7 @@ mod_to_filename(TaskDir, M) -> process(Coverage) -> process(Coverage, {0, 0}). -process([], {0, 0}) -> - "0%"; -process([], {Cov, Not}) -> - integer_to_list(trunc((Cov / (Cov + Not)) * 100)) ++ "%"; +process([], Acc) -> Acc; %% line 0 is a line added by eunit and never executed so ignore it process([{{_, 0}, _}|Rest], Acc) -> process(Rest, Acc); process([{_, {Cov, Not}}|Rest], {Covered, NotCovered}) -> @@ -208,20 +205,19 @@ process([{_, {Cov, Not}}|Rest], {Covered, NotCovered}) -> print_analysis(_, false) -> ok; print_analysis(Analysis, true) -> {_, CoverFiles, Stats} = lists:keyfind("aggregate", 1, Analysis), - ConsoleStats = [ {atom_to_list(M), C} || {M, C, _} <- Stats ], - Table = format_table(ConsoleStats, CoverFiles), + Table = format_table(Stats, CoverFiles), io:format("~ts", [Table]). format_table(Stats, CoverFiles) -> - MaxLength = max(lists:foldl(fun max_length/2, 0, Stats), 20), + MaxLength = lists:max([20 | lists:map(fun({M, _, _}) -> mod_length(M) end, Stats)]), Header = header(MaxLength), Separator = separator(MaxLength), TotalLabel = format("total", MaxLength), TotalCov = format(calculate_total(Stats), 8), [io_lib:format("~ts~n~ts~n~ts~n", [Separator, Header, Separator]), - lists:map(fun({Mod, Coverage}) -> + lists:map(fun({Mod, Coverage, _}) -> Name = format(Mod, MaxLength), - Cov = format(Coverage, 8), + Cov = format(percentage(Coverage), 8), io_lib:format(" | ~ts | ~ts |~n", [Name, Cov]) end, Stats), io_lib:format("~ts~n", [Separator]), @@ -232,12 +228,8 @@ format_table(Stats, CoverFiles) -> io_lib:format(" ~ts~n", [File]) end, CoverFiles)]. -max_length({ModName, _}, Min) -> - Length = length(lists:flatten(ModName)), - case Length > Min of - true -> Length; - false -> Min - end. +mod_length(Mod) when is_atom(Mod) -> mod_length(atom_to_list(Mod)); +mod_length(Mod) -> length(Mod). header(Width) -> [" | ", format("module", Width), " | ", format("coverage", 8), " |"]. @@ -247,17 +239,17 @@ separator(Width) -> format(String, Width) -> io_lib:format("~*.ts", [Width, String]). -calculate_total(Stats) when length(Stats) =:= 0 -> - "0%"; calculate_total(Stats) -> - TotalStats = length(Stats), - TotalCovInt = round(lists:foldl( - fun({_Mod, Coverage, _File}, Acc) -> - Acc + (list_to_integer(string:strip(Coverage, right, $%)) / TotalStats); - ({_Mod, Coverage}, Acc) -> - Acc + (list_to_integer(string:strip(Coverage, right, $%)) / TotalStats) - end, 0, Stats)), - integer_to_list(TotalCovInt) ++ "%". + percentage(lists:foldl( + fun({_Mod, {Cov, Not}, _File}, {CovAcc, NotAcc}) -> + {CovAcc + Cov, NotAcc + Not} + end, + {0, 0}, + Stats + )). + +percentage({_, 0}) -> "100%"; +percentage({Cov, Not}) -> integer_to_list(trunc((Cov / (Cov + Not)) * 100)) ++ "%". write_index(State, Coverage) -> CoverDir = cover_dir(State), @@ -287,7 +279,7 @@ write_index_section(F, [{Section, DataFile, Mods}|Rest]) -> FmtLink = fun({Mod, Cov, Report}) -> ?FMT("<tr><td><a href='~ts'>~ts</a></td><td>~ts</td>\n", - [strip_coverdir(Report), Mod, Cov]) + [strip_coverdir(Report), Mod, percentage(Cov)]) end, lists:foreach(fun(M) -> ok = file:write(F, FmtLink(M)) end, Mods), ok = file:write(F, ?FMT("<tr><td><strong>Total</strong></td><td>~ts</td>\n", diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl index cffbbdc..1da70f8 100644 --- a/src/rebar_prv_escriptize.erl +++ b/src/rebar_prv_escriptize.erl @@ -114,7 +114,7 @@ escriptize(State0, App) -> EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")), ExtraFiles = usort(InclBeams ++ InclExtra), - Files = get_nonempty(EbinFiles ++ ExtraFiles), + Files = get_nonempty(EbinFiles ++ (ExtraFiles -- EbinFiles)), % drop dupes DefaultEmuArgs = ?FMT("%%! -escript main ~ts -pz ~ts/~ts/ebin\n", [AppNameStr, AppNameStr, AppNameStr]), diff --git a/test/rebar_profiles_SUITE.erl b/test/rebar_profiles_SUITE.erl index 9ffaf98..11aca6a 100644 --- a/test/rebar_profiles_SUITE.erl +++ b/test/rebar_profiles_SUITE.erl @@ -7,6 +7,7 @@ all/0, profile_new_key/1, profile_merge_keys/1, + profile_merge_umbrella_keys/1, explicit_profile_deduplicate_deps/1, implicit_profile_deduplicate_deps/1, all_deps_code_paths/1, @@ -33,7 +34,8 @@ -include_lib("kernel/include/file.hrl"). all() -> - [profile_new_key, profile_merge_keys, all_deps_code_paths, profile_merges, + [profile_new_key, profile_merge_keys, profile_merge_umbrella_keys, + all_deps_code_paths, profile_merges, explicit_profile_deduplicate_deps, implicit_profile_deduplicate_deps, same_profile_deduplication, stack_deduplication, add_to_profile, add_to_existing_profile, @@ -118,6 +120,35 @@ profile_merge_keys(Config) -> ,{dep, "a", "1.0.0"} ,{dep, "b", "2.0.0"}]}). +profile_merge_umbrella_keys(Config) -> + AppDir = ?config(apps, Config), + ct:pal("Path: ~s", [AppDir]), + Name = rebar_test_utils:create_random_name("profile_merge_umbrella_keys"), + Vsn = rebar_test_utils:create_random_vsn(), + SubAppDir = filename:join([AppDir, "apps", Name]), + + RebarConfig = [{vals, [{a,1},{b,1}]}, + {profiles, + [{ct, + [{vals, [{a,1},{b,2}]}]}]}], + + SubRebarConfig = [{vals, []}, + {profiles, [{ct, [{vals, [{c,1}]}]}]}], + + rebar_test_utils:create_app(SubAppDir, Name, Vsn, [kernel, stdlib]), + rebar_test_utils:create_config(SubAppDir, SubRebarConfig), + {ok, RebarConfigRead} = file:consult(rebar_test_utils:create_config(AppDir, RebarConfig)), + + {ok, State} = rebar_test_utils:run_and_check( + Config, RebarConfigRead, ["as", "ct", "compile"], return + ), + + [ProjectApp] = rebar_state:project_apps(State), + ?assertEqual(Name, binary_to_list(rebar_app_info:name(ProjectApp))), + Opts = rebar_app_info:opts(ProjectApp), + ?assertEqual([{a,1},{b,2},{b,1},{c,1}], dict:fetch(vals, Opts)), + ok. + explicit_profile_deduplicate_deps(Config) -> AppDir = ?config(apps, Config), diff --git a/test/rebar_release_SUITE.erl b/test/rebar_release_SUITE.erl index e41339b..1bcc61e 100644 --- a/test/rebar_release_SUITE.erl +++ b/test/rebar_release_SUITE.erl @@ -200,13 +200,34 @@ profile_overlays(Config) -> AppDir = ?config(apps, Config), Name = ?config(name, Config), Vsn = "1.0.0", + file:write_file(filename:join(AppDir, "dev.file"), "dev.\n"), + file:write_file(filename:join(AppDir, "prod.file"), "prod.\n"), + file:write_file(filename:join(AppDir, "dev.vars"), "{env, \"dev\"}.\n"), + file:write_file(filename:join(AppDir, "prod.vars"), "{env, \"prod\"}.\n"), {ok, RebarConfig} = - file:consult(rebar_test_utils:create_config(AppDir, - [{relx, [{release, {list_to_atom(Name), Vsn}, - [list_to_atom(Name)]}, - {overlay, [{mkdir, "randomdir"}]}, - {lib_dirs, [AppDir]}]}, - {profiles, [{prod, [{relx, [{overlay, [{mkdir, "otherrandomdir"}]}]}]}]}])), + file:consult(rebar_test_utils:create_config(AppDir, + %% Paths are relative, but to cwd in relx, not the project root as + %% seen by rebar3 (in non-test cases, they're the same). + %% Work around by being explicit. + [{relx, [{release, {list_to_atom(Name), Vsn}, + [list_to_atom(Name)]}, + {overlay_vars, filename:join(AppDir, "dev.vars")}, + {overlay, [{mkdir, "randomdir"}, + {copy, filename:join(AppDir,"./dev.file"), "profile.file"}, + {copy, filename:join(AppDir,"./dev.file"), "{{env}}.file"}, + {chmod, 8#00770, "profile.file"}]}, + {lib_dirs, [AppDir]}]}, + {profiles, [{prod, + [{relx, [ + {overlay_vars, filename:join(AppDir, "prod.vars")}, + {overlay, [{mkdir, "otherrandomdir"}, + {copy, filename:join(AppDir, "./prod.file"), "{{env}}.file"}, + {copy, filename:join(AppDir, "./prod.file"), "profile.file"}, + {chmod, 8#00770, "profile.file"}]} + + ]}] + }]} + ])), ReleaseDir = filename:join([AppDir, "./_build/prod/rel/", Name]), @@ -216,7 +237,12 @@ profile_overlays(Config) -> {ok, [{release, list_to_atom(Name), Vsn, false}, {dir, filename:join(ReleaseDir, "otherrandomdir")}, {dir, filename:join(ReleaseDir, "randomdir")}]} - ). + ), + ?assertMatch({ok,[prod]}, + file:consult(filename:join(ReleaseDir, "profile.file"))), + ?assertMatch({ok,[prod]}, + file:consult(filename:join(ReleaseDir, "prod.file"))), + ok. profile_overlay_merge (_Config) -> % when profile and relx overlays both exist, the profile overlays should be |