summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--appveyor.yml20
-rw-r--r--priv/templates/umbrella.template15
-rw-r--r--src/rebar3.erl4
-rw-r--r--src/rebar_opts.erl36
-rw-r--r--src/rebar_prv_cover.erl44
-rw-r--r--src/rebar_prv_escriptize.erl2
-rw-r--r--test/rebar_profiles_SUITE.erl33
-rw-r--r--test/rebar_release_SUITE.erl40
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