diff options
-rw-r--r-- | .cirrus.yml | 28 | ||||
-rwxr-xr-x | bootstrap | 2 | ||||
-rw-r--r-- | rebar.config | 8 | ||||
-rw-r--r-- | rebar.lock | 16 | ||||
-rw-r--r-- | src/rebar_app_utils.erl | 1 | ||||
-rw-r--r-- | src/rebar_compiler.erl | 67 | ||||
-rw-r--r-- | src/rebar_compiler_erl.erl | 12 | ||||
-rw-r--r-- | src/rebar_file_utils.erl | 15 | ||||
-rw-r--r-- | src/rebar_packages.erl | 6 | ||||
-rw-r--r-- | src/rebar_prv_deps.erl | 185 | ||||
-rw-r--r-- | src/rebar_prv_dialyzer.erl | 51 | ||||
-rw-r--r-- | src/rebar_prv_escriptize.erl | 22 | ||||
-rw-r--r-- | src/rebar_prv_eunit.erl | 30 | ||||
-rw-r--r-- | src/rebar_prv_shell.erl | 76 | ||||
-rw-r--r-- | src/rebar_string.erl | 29 | ||||
-rw-r--r-- | src/rebar_templater.erl | 29 | ||||
-rw-r--r-- | test/rebar_deps_SUITE.erl | 71 | ||||
-rw-r--r-- | test/rebar_dialyzer_SUITE.erl | 41 |
18 files changed, 521 insertions, 168 deletions
diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..44c8229 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,28 @@ +test_task: + container: + matrix: + - image: erlang:21 + - image: erlang:20 + - image: erlang:19 + - image: erlang:18 + - image: erlang:17 + test_script: | + ./bootstrap + ./rebar3 ct + +osx_test_task: + osx_instance: + image: mojave-base + install_script: brew install erlang + test_script: | + ./bootstrap + ./rebar3 ct + +windows_test_task: + windows_container: + image: cirrusci/windowsservercore:2019 + os_version: 2019 + install_script: choco install -y erlang + test_script: | + ./bootstrap + ./rebar3 ct @@ -35,7 +35,7 @@ main(_) -> bootstrap_rebar3(), %% Build rebar.app from rebar.app.src - {ok, App} = rebar_app_info:new(rebar, "3.9.1", filename:absname("_build/default/lib/rebar/")), + {ok, App} = rebar_app_info:new(rebar, "3.10.0", filename:absname("_build/default/lib/rebar/")), rebar_otp_app:compile(rebar_state:new(), App), %% Because we are compiling files that are loaded already we want to silence diff --git a/rebar.config b/rebar.config index 6d80bd9..584ad21 100644 --- a/rebar.config +++ b/rebar.config @@ -2,13 +2,13 @@ %% ex: ts=4 sw=4 ft=erlang et {deps, [{erlware_commons, "1.3.1"}, - {ssl_verify_fun, "1.1.3"}, - {certifi, "2.3.1"}, + {ssl_verify_fun, "1.1.4"}, + {certifi, "2.5.1"}, {parse_trans, "3.3.0"}, % force otp-21 compat {providers, "1.7.0"}, {getopt, "1.0.1"}, - {bbmustache, "1.6.0"}, - {relx, "3.29.0"}, + {bbmustache, "1.6.1"}, + {relx, "3.31.0"}, {cf, "0.2.2"}, {cth_readable, "1.4.3"}, {eunit_formatters, "0.5.0"}]}. @@ -1,6 +1,6 @@ {"1.1.0", -[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.6.0">>},0}, - {<<"certifi">>,{pkg,<<"certifi">>,<<"2.3.1">>},0}, +[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.6.1">>},0}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.1">>},0}, {<<"cf">>,{pkg,<<"cf">>,<<"0.2.2">>},0}, {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.4.3">>},0}, {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.3.1">>},0}, @@ -8,12 +8,12 @@ {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.7.0">>},0}, - {<<"relx">>,{pkg,<<"relx">>,<<"3.29.0">>},0}, - {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.3">>},0}]}. + {<<"relx">>,{pkg,<<"relx">>,<<"3.31.0">>},0}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.4">>},0}]}. [ {pkg_hash,[ - {<<"bbmustache">>, <<"7AC372AEC621A69C369DF237FBD9986CAABCDD6341089FE5F42E5A7A4AC706B8">>}, - {<<"certifi">>, <<"D0F424232390BF47D82DA8478022301C561CF6445B5B5FB6A84D49A9E76D2639">>}, + {<<"bbmustache">>, <<"9FB63FA60BD53AFBF47F02E6D8BD6B2BEAFC068E02E20975254DC7461FD4F397">>}, + {<<"certifi">>, <<"867CE347F7C7D78563450A18A6A28A8090331E77FA02380B4A21962A65D36EE5">>}, {<<"cf">>, <<"7F2913FFF90ABCABD0F489896CFEB0B0674F6C8DF6C10B17A83175448029896C">>}, {<<"cth_readable">>, <<"5B8117797E89D5074022CA21C7867216B796C5C04895B71C097EC8BF0F8A55C1">>}, {<<"erlware_commons">>, <<"0CE192AD69BC6FD0880246D852D0ECE17631E234878011D1586E053641ED4C04">>}, @@ -21,6 +21,6 @@ {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}, {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, {<<"providers">>, <<"BBF730563914328EC2511D205E6477A94831DB7297DE313B3872A2B26C562EAB">>}, - {<<"relx">>, <<"CEDB052F2417784C939AE23FE2EF2C243B6A272C419B85510B33BC1C55728082">>}, - {<<"ssl_verify_fun">>, <<"6C49665D4326E26CD4A5B7BD54AA442B33DADFB7C5D59A0D0CD0BF5534BBFBD7">>}]} + {<<"relx">>, <<"7EBA13DFFC4EEA9FBFAE2B42B739AFDE268E07C3EE7207872CD74782A4913455">>}, + {<<"ssl_verify_fun">>, <<"F0EAFFF810D2041E93F915EF59899C923F4568F4585904D010387ED74988E77B">>}]} ]. diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl index bb706fd..91c095c 100644 --- a/src/rebar_app_utils.erl +++ b/src/rebar_app_utils.erl @@ -34,6 +34,7 @@ validate_application_info/2, parse_deps/5, parse_deps/6, + parse_dep/6, expand_deps_sources/2, dep_to_app/7, format_error/1]). diff --git a/src/rebar_compiler.erl b/src/rebar_compiler.erl index 02c74db..55666ba 100644 --- a/src/rebar_compiler.erl +++ b/src/rebar_compiler.erl @@ -21,7 +21,10 @@ out_mappings => out_mappings()}. -callback needed_files(digraph:graph(), [file:filename()], out_mappings(), rebar_app_info:t()) -> - {{[file:filename()], term()}, {[file:filename()], term()}}. + {{[file:filename()], term()}, % ErlFirstFiles (erl_opts global priority) + {[file:filename()] | % [Sequential] + {[file:filename()], [file:filename()]}, % {Sequential, Parallel} + term()}}. -callback dependencies(file:filename(), file:dirname(), [file:dirname()]) -> [file:filename()]. -callback compile(file:filename(), out_mappings(), rebar_dict(), list()) -> ok | {ok, [string()]} | {ok, [string()], [string()]}. @@ -77,7 +80,13 @@ run(CompilerMod, AppInfo, Label) -> true = digraph:delete(G), compile_each(FirstFiles, FirstFileOpts, BaseOpts, Mappings, CompilerMod), - compile_each(RestFiles, Opts, BaseOpts, Mappings, CompilerMod). + case RestFiles of + {Sequential, Parallel} -> % new parallelizable form + compile_each(Sequential, Opts, BaseOpts, Mappings, CompilerMod), + compile_parallel(Parallel, Opts, BaseOpts, Mappings, CompilerMod); + _ when is_list(RestFiles) -> % traditional sequential build + compile_each(RestFiles, Opts, BaseOpts, Mappings, CompilerMod) + end. compile_each([], _Opts, _Config, _Outs, _CompilerMod) -> ok; @@ -99,6 +108,60 @@ compile_each([Source | Rest], Opts, Config, Outs, CompilerMod) -> end, compile_each(Rest, Opts, Config, Outs, CompilerMod). +compile_worker(QueuePid, Opts, Config, Outs, CompilerMod) -> + QueuePid ! self(), + receive + {compile, Source} -> + Result = CompilerMod:compile(Source, Outs, Config, Opts), + QueuePid ! {Result, Source}, + compile_worker(QueuePid, Opts, Config, Outs, CompilerMod); + empty -> + ok + end. + +compile_parallel([], _Opts, _BaseOpts, _Mappings, _CompilerMod) -> + ok; +compile_parallel(Targets, Opts, BaseOpts, Mappings, CompilerMod) -> + Self = self(), + F = fun() -> compile_worker(Self, Opts, BaseOpts, Mappings, CompilerMod) end, + Jobs = min(length(Targets), erlang:system_info(schedulers)), + ?DEBUG("Starting ~B compile worker(s)", [Jobs]), + Pids = [spawn_monitor(F) || _I <- lists:seq(1, Jobs)], + compile_queue(Targets, Pids, Opts, BaseOpts, Mappings, CompilerMod). + +compile_queue([], [], _Opts, _Config, _Outs, _CompilerMod) -> + ok; +compile_queue(Targets, Pids, Opts, Config, Outs, CompilerMod) -> + receive + Worker when is_pid(Worker), Targets =:= [] -> + Worker ! empty, + compile_queue(Targets, Pids, Opts, Config, Outs, CompilerMod); + Worker when is_pid(Worker) -> + Worker ! {compile, hd(Targets)}, + compile_queue(tl(Targets), Pids, Opts, Config, Outs, CompilerMod); + {ok, Source} -> + ?DEBUG("~sCompiled ~s", [rebar_utils:indent(1), filename:basename(Source)]), + compile_queue(Targets, Pids, Opts, Config, Outs, CompilerMod); + {{ok, Warnings}, Source} -> + report(Warnings), + ?DEBUG("~sCompiled ~s", [rebar_utils:indent(1), filename:basename(Source)]), + compile_queue(Targets, Pids, Opts, Config, Outs, CompilerMod); + {skipped, Source} -> + ?DEBUG("~sSkipped ~s", [rebar_utils:indent(1), filename:basename(Source)]), + compile_queue(Targets, Pids, Opts, Config, Outs, CompilerMod); + {Error, Source} -> + NewSource = format_error_source(Source, Config), + ?ERROR("Compiling ~ts failed", [NewSource]), + maybe_report(Error), + ?FAIL; + {'DOWN', Mref, _, Pid, normal} -> + Pids2 = lists:delete({Pid, Mref}, Pids), + compile_queue(Targets, Pids2, Opts, Config, Outs, CompilerMod); + {'DOWN', _Mref, _, _Pid, Info} -> + ?ERROR("Compilation failed: ~p", [Info]), + ?FAIL + end. + %% @doc remove compiled artifacts from an AppDir. -spec clean([module()], rebar_app_info:t()) -> 'ok'. clean(Compilers, AppInfo) -> diff --git a/src/rebar_compiler_erl.erl b/src/rebar_compiler_erl.erl index 759305c..1ad16d8 100644 --- a/src/rebar_compiler_erl.erl +++ b/src/rebar_compiler_erl.erl @@ -55,7 +55,14 @@ needed_files(Graph, FoundFiles, _, AppInfo) -> {ErlFirstFiles, ErlOptsFirst} = erl_first_files(RebarOpts, ErlOpts, Dir, NeededErlFiles), SubGraph = digraph_utils:subgraph(Graph, NeededErlFiles), DepErlsOrdered = digraph_utils:topsort(SubGraph), - OtherErls = lists:reverse(DepErlsOrdered), + %% Break out the files required by other modules from those + %% that none other depend of; the former must be sequentially + %% built, the rest is parallelizable. + OtherErls = lists:partition( + fun(Erl) -> digraph:in_degree(Graph, Erl) > 0 end, + lists:reverse([Dep || Dep <- DepErlsOrdered, + not lists:member(Dep, ErlFirstFiles)]) + ), PrivIncludes = [{i, filename:join(OutDir, Src)} || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])], @@ -64,8 +71,7 @@ needed_files(Graph, FoundFiles, _, AppInfo) -> true = digraph:delete(SubGraph), {{ErlFirstFiles, ErlOptsFirst ++ AdditionalOpts}, - {[Erl || Erl <- OtherErls, - not lists:member(Erl, ErlFirstFiles)], ErlOpts ++ AdditionalOpts}}. + {OtherErls, ErlOpts ++ AdditionalOpts}}. dependencies(Source, SourceDir, Dirs) -> {ok, Fd} = file:open(Source, [read]), diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl index 0e0dfe3..b2f34f0 100644 --- a/src/rebar_file_utils.erl +++ b/src/rebar_file_utils.erl @@ -28,6 +28,7 @@ -export([try_consult/1, consult_config/2, + consult_config_terms/2, format_error/1, symlink_or_copy/2, rm_rf/1, @@ -66,6 +67,8 @@ try_consult(File) -> throw(?PRV_ERROR({bad_term_file, File, Reason})) end. +%% @doc Parse a sys.config file and return the configuration terms +%% for all its potentially nested configs. -spec consult_config(rebar_state:t(), string()) -> [[tuple()]]. consult_config(State, Filename) -> Fullpath = filename:join(rebar_dir:root_dir(State), Filename), @@ -74,6 +77,18 @@ consult_config(State, Filename) -> [T] -> T; [] -> [] end, + consult_config_terms(State, Config). + +%% @doc From a parsed sys.config file, expand all the terms to include +%% its potential nested configs. It is also possible that no sub-terms +%% (i.e. the config file does not refer to "some/other/file.config") +%% that the input term is returned as-is. +%% +%% This function is added mostly to help with variable substitution +%% and evaluation of 'sys.config.src' files, giving a way to handle +%% expansion that is separate from regular config handling. +-spec consult_config_terms(rebar_state:t(), [tuple()]) -> [[tuple()]]. +consult_config_terms(State, Config) -> JoinedConfig = lists:flatmap( fun (SubConfig) when is_list(SubConfig) -> case lists:suffix(".config", SubConfig) of diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index a260e47..c7bbfac 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -238,7 +238,11 @@ update_package(Name, RepoConfig=#{name := Repo}, State) -> _ = insert_releases(Name, Releases, Repo, ?PACKAGE_TABLE), {ok, RegistryDir} = rebar_packages:registry_dir(State), PackageIndex = filename:join(RegistryDir, ?INDEX_FILE), - ok = ets:tab2file(?PACKAGE_TABLE, PackageIndex); + case ets:tab2file(?PACKAGE_TABLE, PackageIndex) of + ok -> ok; + {error, Error} -> + ?WARN("Failed to update package index at ~p: ~p", [PackageIndex, Error]) + end; {error, unverified} -> ?WARN(unverified_repo_message(), [Repo]), fail; diff --git a/src/rebar_prv_deps.erl b/src/rebar_prv_deps.erl index 577a859..d00a1c8 100644 --- a/src/rebar_prv_deps.erl +++ b/src/rebar_prv_deps.erl @@ -9,7 +9,7 @@ -include("rebar.hrl"). -define(PROVIDER, deps). --define(DEPS, [app_discovery]). +-define(DEPS, [install_deps]). -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> @@ -22,98 +22,127 @@ init(State) -> {deps, ?DEPS}, {example, "rebar3 deps"}, {short_desc, "List dependencies"}, - {desc, "List dependencies. Those not matching lock files " - "are followed by an asterisk (*)."}, + {desc, "List dependencies. Those not matching " + "the config file are followed by " + "an asterisk (*)."}, {opts, []}])), {ok, State1}. + -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> Profiles = rebar_state:current_profiles(State), - List = [{Profile, rebar_state:get(State, {deps, Profile}, [])} - || Profile <- Profiles], - [display(State, Profile, Deps) || {Profile, Deps} <- List], + [display(State, Profile) || Profile <- Profiles], {ok, State}. + -spec format_error(any()) -> iolist(). format_error(Reason) -> io_lib:format("~p", [Reason]). -display(State, default, Deps) -> - NewDeps = merge(Deps, rebar_state:get(State, deps, [])), - display_deps(State, NewDeps), + +display(State, Profile = default) -> + display_profile_deps(State, Profile), ?CONSOLE("", []); -display(State, Profile, Deps) -> +display(State, Profile) -> ?CONSOLE("-- ~p --", [Profile]), - display_deps(State, Deps), + display_profile_deps(State, Profile), ?CONSOLE("", []). -merge(Deps, SourceDeps) -> - merge1(dedup([normalize(Dep) || Dep <- Deps]), - [normalize(Dep) || Dep <- SourceDeps]). - -normalize(Name) when is_binary(Name) -> - Name; -normalize(Name) when is_atom(Name) -> - atom_to_binary(Name, unicode); -normalize(Dep) when is_tuple(Dep) -> - Name = element(1, Dep), - setelement(1, Dep, normalize(Name)). - -merge1(Deps, SourceDeps) -> - Names = [name(Dep) || Dep <- Deps], - ToAdd = [Dep || Dep <- SourceDeps, - not lists:member(name(Dep), Names)], - Deps ++ ToAdd. - -%% Keep the latter one as locks come after regular deps in the list. -%% This is totally not safe as an assumption, but it's what we got. -%% We do this by comparing the current element and looking if a -%% similar named one happens later. If so, drop the current one. -dedup(Deps) -> dedup(Deps, [name(Dep) || Dep <- Deps]). - -dedup([], []) -> []; -dedup([Dep|Deps], [Name|DepNames]) -> - case lists:member(Name, DepNames) of - true -> dedup(Deps, DepNames); - false -> [Dep | dedup(Deps, DepNames)] + +display_profile_deps(State, Profile) -> + DepsDir = rebar_prv_install_deps:profile_dep_dir(State, Profile), + + ProfileDeps = rebar_state:get(State, {deps, Profile}, []), + % ProfileDeps include those deps from rebar.lock that have been + % removed from rebar.config + ConfiguredDeps = [parse_dep_without_locks(DepsDir, Dep, State) + || Dep <- ProfileDeps], + LockedDepsMap = locked_deps_map(State, Profile), + [display_dep(State, Dep, LockedDepsMap) || Dep <- ConfiguredDeps]. + + +parse_dep_without_locks(DepsDir, Dep, State) -> + ParsedDep = rebar_app_utils:parse_dep(Dep, root, DepsDir, State, [], 0), + case Dep of + {_Name, Src, Level} when is_tuple(Src), is_integer(Level) -> + % This Dep is not in rebar.config but in rebar.lock + rebar_app_info:source(ParsedDep, undefined); + _ -> + rebar_app_utils:expand_deps_sources(ParsedDep, State) + end. + + +locked_deps_map(State, Profile) -> + ParsedDeps = rebar_state:get(State, {parsed_deps, Profile}, []), + lists:foldl(fun(Dep, DepsIn) -> + case rebar_app_info:is_lock(Dep) of + true -> + DepName = rebar_app_info:name(Dep), + maps:put(rebar_utils:to_binary(DepName), Dep, DepsIn); + _ -> + DepsIn + end + end, maps:new(), ParsedDeps). + + +display_dep(State, Dep, LockedDeps) -> + Name = rebar_utils:to_binary(rebar_app_info:name(Dep)), + NeedsUpdate = rebar_fetch:needs_update(Dep, State), + Source = rebar_app_info:source(Dep), + LockedSource = case maps:get(Name, LockedDeps, undefined) of + undefined -> undefined; + LockedDep -> rebar_app_info:source(LockedDep) + end, + + display_dep_line(Name, NeedsUpdate, source_text(LockedSource), source_text(Source)). + + +% Dep is a checkout +display_dep_line(Name, _NeedsUpdate, _LockedSource, Source = checkout) -> + ?CONSOLE("~ts* (~ts)", [Name, Source]); + +% Dep exists only in lock file +display_dep_line(Name, _NeedsUpdate, LockedSource, _Source = undefined) -> + ?CONSOLE("~ts* (locked ~ts <> none)", [Name, LockedSource]); + +% Dep not locked, report whether the disk copy matches the Source +display_dep_line(Name, true, undefined, Source) -> + ?CONSOLE("~ts* (~ts)", [Name, Source]); +display_dep_line(Name, _, undefined, Source) -> + ?CONSOLE("~ts (~ts)", [Name, Source]); + +% Dep locked, install_deps provider should have had updated the disk copy with +% the locked version +display_dep_line(Name, false, _LockedSource, Source) -> + % dep locked and no need to update (LockedSource and Source might not match + % because of one using {ref, X} and the other {tag, Y}) + ?CONSOLE("~ts (locked ~ts)", [Name, Source]); +display_dep_line(Name, _NeedsUpdate, LockedSource, Source) -> + % dep locked with mismatching lock and config files + ?CONSOLE("~ts* (locked ~ts <> ~ts)", [Name, LockedSource, Source]). + + +source_text(Source) when is_list(Source); is_atom(Source) -> + Source; +source_text({pkg, _Name, Vsn, _Hash, _RepoConfig}) -> + source_text({pkg, _Name, Vsn, _Hash}); +source_text({pkg, _Name, Vsn, _Hash}) -> + [<<"package">>, " ", rebar_utils:to_binary(Vsn)]; +source_text(Source) when is_tuple(Source), tuple_size(Source) < 3 -> + element(1, Source); +source_text(Source) when is_tuple(Source) -> + Type = element(1, Source), + case element(3, Source) of + {ref , Ref} -> + SmallRef = case rebar_utils:to_binary(Ref) of + <<R:7/binary, _/binary>> -> <<R/binary, "...">>; + R -> R + end, + [atom_to_binary(Type, unicode), " source ", SmallRef]; + {_ , Vsn} -> + [atom_to_binary(Type, unicode), " source ", rebar_utils:to_binary(Vsn)]; + _ -> + [atom_to_binary(Type, unicode), " source"] end. -name(T) when is_tuple(T) -> element(1, T); -name(B) when is_binary(B) -> B. - -display_deps(State, Deps) -> - lists:foreach(fun(Dep) -> display_dep(State, Dep) end, Deps). - -%% packages -display_dep(_State, {Name, Vsn}) when is_list(Vsn) -> - ?CONSOLE("~ts* (package ~ts)", [rebar_utils:to_binary(Name), rebar_utils:to_binary(Vsn)]); -display_dep(_State, Name) when is_binary(Name) -> - ?CONSOLE("~ts* (package)", [Name]); -display_dep(_State, {Name, Source}) when is_tuple(Source) -> - ?CONSOLE("~ts* (~ts source)", [rebar_utils:to_binary(Name), type(Source)]); -display_dep(_State, {Name, _Vsn, Source}) when is_tuple(Source) -> - ?CONSOLE("~ts* (~ts source)", [rebar_utils:to_binary(Name), type(Source)]); -display_dep(_State, {Name, _Vsn, Source, _Opts}) when is_tuple(Source) -> - ?CONSOLE("~ts* (~ts source)", [rebar_utils:to_binary(Name), type(Source)]); -%% Locked -display_dep(State, {Name, _Source={pkg, _, Vsn}, Level}) when is_integer(Level) -> - DepsDir = rebar_dir:deps_dir(State), - AppDir = filename:join([DepsDir, rebar_utils:to_binary(Name)]), - {ok, AppInfo} = rebar_app_info:discover(AppDir), - NeedsUpdate = case rebar_fetch:needs_update(AppInfo, State) of - true -> "*"; - false -> "" - end, - ?CONSOLE("~ts~ts (locked package ~ts)", [Name, NeedsUpdate, Vsn]); -display_dep(State, {Name, Source, Level}) when is_tuple(Source), is_integer(Level) -> - DepsDir = rebar_dir:deps_dir(State), - AppDir = filename:join([DepsDir, rebar_utils:to_binary(Name)]), - {ok, AppInfo} = rebar_app_info:discover(AppDir), - NeedsUpdate = case rebar_fetch:needs_update(AppInfo, State) of - true -> "*"; - false -> "" - end, - ?CONSOLE("~ts~ts (locked ~ts source)", [Name, NeedsUpdate, type(Source)]). - -type(Source) when is_tuple(Source) -> element(1, Source). diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 585051c..82b4012 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -23,7 +23,11 @@ -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> Opts = [{update_plt, $u, "update-plt", boolean, "Enable updating the PLT. Default: true"}, - {succ_typings, $s, "succ-typings", boolean, "Enable success typing analysis. Default: true"}], + {succ_typings, $s, "succ-typings", boolean, "Enable success typing analysis. Default: true"}, + {base_plt_location, undefined, "base-plt-location", string, "The location of base PLT file, defaults to $HOME/.cache/rebar3"}, + {plt_location, undefined, "plt-location", string, "The location of the PLT file, defaults to the profile's base directory"}, + {plt_prefix, undefined, "plt-prefix", string, "The prefix to the PLT file, defaults to \"rebar3\"" }, + {base_plt_prefix, undefined, "base-plt-prefix", string, "The prefix to the base PLT file, defaults to \"rebar3\"" }], State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {bare, true}, @@ -87,10 +91,11 @@ do(State) -> ?INFO("Dialyzer starting, this may take a while...", []), rebar_paths:unset_paths([plugins], State), % no plugins in analysis rebar_paths:set_paths([deps], State), - Plt = get_plt(State), + {Args, _} = rebar_state:command_parsed_args(State), + Plt = get_plt(Args, State), try - do(State, Plt) + do(Args, State, Plt) catch throw:{dialyzer_error, Error} -> ?PRV_ERROR({error_processing_apps, Error}); @@ -134,10 +139,10 @@ format_error(Reason) -> %% Internal functions -get_plt(State) -> - Prefix = get_config(State, plt_prefix, ?PLT_PREFIX), +get_plt(Args, State) -> + Prefix = proplists:get_value(plt_prefix, Args, get_config(State, plt_prefix, ?PLT_PREFIX)), Name = plt_name(Prefix), - case get_config(State, plt_location, local) of + case proplists:get_value(plt_location, Args, get_config(State, plt_location, local)) of local -> BaseDir = rebar_dir:base_dir(State), filename:join(BaseDir, Name); @@ -148,10 +153,10 @@ get_plt(State) -> plt_name(Prefix) -> Prefix ++ "_" ++ rebar_utils:otp_release() ++ "_plt". -do(State, Plt) -> +do(Args, State, Plt) -> Output = get_output_file(State), - {PltWarnings, State1} = update_proj_plt(State, Plt, Output), - {Warnings, State2} = succ_typings(State1, Plt, Output), + {PltWarnings, State1} = update_proj_plt(Args, State, Plt, Output), + {Warnings, State2} = succ_typings(Args, State1, Plt, Output), case PltWarnings + Warnings of 0 -> {ok, State2}; @@ -174,23 +179,22 @@ get_output_file(State) -> default_output_file() -> rebar_utils:otp_release() ++ ".dialyzer_warnings". -update_proj_plt(State, Plt, Output) -> - {Args, _} = rebar_state:command_parsed_args(State), +update_proj_plt(Args, State, Plt, Output) -> case proplists:get_value(update_plt, Args) of false -> {0, State}; _ -> - do_update_proj_plt(State, Plt, Output) + do_update_proj_plt(Args, State, Plt, Output) end. -do_update_proj_plt(State, Plt, Output) -> +do_update_proj_plt(Args, State, Plt, Output) -> ?INFO("Updating plt...", []), Files = proj_plt_files(State), case read_plt(State, Plt) of {ok, OldFiles} -> check_plt(State, Plt, Output, OldFiles, Files); error -> - build_proj_plt(State, Plt, Output, Files) + build_proj_plt(Args, State, Plt, Output, Files) end. proj_plt_files(State) -> @@ -373,8 +377,8 @@ run_plt(State, Plt, Output, Analysis, Files) -> {files, Files}], run_dialyzer(State, Opts, Output). -build_proj_plt(State, Plt, Output, Files) -> - BasePlt = get_base_plt(State), +build_proj_plt(Args, State, Plt, Output, Files) -> + BasePlt = get_base_plt(Args, State), ?INFO("Updating base plt...", []), BaseFiles = base_plt_files(State), {BaseWarnings, State1} = update_base_plt(State, BasePlt, Output, BaseFiles), @@ -391,10 +395,10 @@ build_proj_plt(State, Plt, Output, Files) -> throw({dialyzer_error, Error}) end. -get_base_plt(State) -> - Prefix = get_config(State, base_plt_prefix, ?PLT_PREFIX), +get_base_plt(Args, State) -> + Prefix = proplists:get_value(base_plt_prefix, Args, get_config(State, base_plt_prefix, ?PLT_PREFIX)), Name = plt_name(Prefix), - case get_config(State, base_plt_location, global) of + case proplists:get_value(base_plt_location, Args, get_config(State, base_plt_location, global)) of global -> GlobalCacheDir = rebar_dir:global_cache_dir(rebar_state:opts(State)), filename:join(GlobalCacheDir, Name); @@ -439,21 +443,20 @@ build_plt(State, Plt, Output, Files) -> {files, Files}], run_dialyzer(State, Opts, Output). -succ_typings(State, Plt, Output) -> - {Args, _} = rebar_state:command_parsed_args(State), +succ_typings(Args, State, Plt, Output) -> case proplists:get_value(succ_typings, Args) of false -> {0, State}; _ -> ?INFO("Doing success typing analysis...", []), Files = proj_files(State), - succ_typings(State, Plt, Output, Files) + succ_typings_(State, Plt, Output, Files) end. -succ_typings(State, Plt, _, []) -> +succ_typings_(State, Plt, _, []) -> ?INFO("Analyzing no files with ~p...", [Plt]), {0, State}; -succ_typings(State, Plt, Output, Files) -> +succ_typings_(State, Plt, Output, Files) -> ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]), Opts = [{analysis_type, succ_typings}, {get_warnings, true}, diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl index fceb65e..d6d9414 100644 --- a/src/rebar_prv_escriptize.erl +++ b/src/rebar_prv_escriptize.erl @@ -63,13 +63,11 @@ desc() -> do(State) -> Providers = rebar_state:providers(State), Cwd = rebar_state:dir(State), - rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State), - ?INFO("Building escript...", []), - Res = case rebar_state:get(State, escript_main_app, undefined) of + AppInfo0 = case rebar_state:get(State, escript_main_app, undefined) of undefined -> case rebar_state:project_apps(State) of - [App] -> - escriptize(State, App); + [AppInfo] -> + AppInfo; _ -> ?PRV_ERROR(no_main_app) end; @@ -77,13 +75,21 @@ do(State) -> AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), case rebar_app_utils:find(rebar_utils:to_binary(Name), AllApps) of {ok, AppInfo} -> - escriptize(State, AppInfo); + AppInfo; _ -> ?PRV_ERROR({bad_name, Name}) end end, - rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State), - Res. + case AppInfo0 of + {error, _} = Err -> + Err; + _ -> + AppInfo1 = rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, AppInfo0, State), + ?INFO("Building escript...", []), + Res = escriptize(State, AppInfo1), + rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, AppInfo1, State), + Res + end. escriptize(State0, App) -> AppName = rebar_app_info:name(App), diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0b00e89..4764439 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -424,17 +424,35 @@ resolve_eunit_opts(State) -> true -> set_verbose(EUnitOpts); false -> EUnitOpts end, - IsVerbose = lists:member(verbose, EUnitOpts1), - case proplists:get_value(eunit_formatters, EUnitOpts1, not IsVerbose) of - true -> custom_eunit_formatters(EUnitOpts1); - false -> EUnitOpts1 + EUnitOpts2 = case proplists:get_value(profile, Opts, false) of + true -> set_profile(EUnitOpts1); + false -> EUnitOpts1 + end, + IsVerbose = lists:member(verbose, EUnitOpts2), + case proplists:get_value(eunit_formatters, Opts, not IsVerbose) of + true -> custom_eunit_formatters(EUnitOpts2); + false -> EUnitOpts2 end. custom_eunit_formatters(Opts) -> + ReportOpts = custom_eunit_report_options(Opts), %% If `report` is already set then treat that like `eunit_formatters` is false case lists:keymember(report, 1, Opts) of true -> Opts; - false -> [no_tty, {report, {eunit_progress, [colored, profile]}} | Opts] + false -> [no_tty, {report, {eunit_progress, ReportOpts}} | Opts] + end. + +custom_eunit_report_options(Opts) -> + case lists:member(profile, Opts) of + true -> [colored, profile]; + false -> [colored] + end. + +set_profile(Opts) -> + %% if `profile` is already set don't set it again + case lists:member(profile, Opts) of + true -> Opts; + false -> [profile] ++ Opts end. set_verbose(Opts) -> @@ -508,6 +526,7 @@ eunit_opts(_State) -> {application, undefined, "application", string, help(app)}, {cover, $c, "cover", boolean, help(cover)}, {cover_export_name, undefined, "cover_export_name", string, help(cover_export_name)}, + {profile, $p, "profile", boolean, help(profile)}, {dir, $d, "dir", string, help(dir)}, {file, $f, "file", string, help(file)}, {module, $m, "module", string, help(module)}, @@ -521,6 +540,7 @@ eunit_opts(_State) -> help(app) -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`."; help(cover) -> "Generate cover data. Defaults to false."; help(cover_export_name) -> "Base name of the coverdata file to write"; +help(profile) -> "Show the slowest tests. Defaults to false."; help(dir) -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; help(file) -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; help(module) -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index c0cfd6d..dd3e6f6 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -36,6 +36,7 @@ format_error/1]). -include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). -define(PROVIDER, shell). -define(DEPS, [compile]). @@ -503,7 +504,12 @@ find_config(State) -> no_value -> no_config; Filename when is_list(Filename) -> - rebar_file_utils:consult_config(State, Filename) + case is_src_config(Filename) of + false -> + rebar_file_utils:consult_config(State, Filename); + true -> + consult_env_config(State, Filename) + end end. -spec first_value([Fun], State) -> no_value | Value when @@ -540,5 +546,69 @@ find_config_rebar(State) -> -spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value. find_config_relx(State) -> - debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value, - "Found config from relx."). + %% The order in relx is to load the src version first; + %% we do the same. + RelxCfg = rebar_state:get(State, relx, []), + Src = debug_get_value(sys_config_src, RelxCfg, no_value, + "Found config.src from relx."), + case Src of + no_value -> + debug_get_value(sys_config, RelxCfg, no_value, + "Found config from relx."); + _ -> + Src + end. + +-spec is_src_config(file:filename()) -> boolean(). +is_src_config(Filename) -> + filename:extension(Filename) =:= ".src". + +-spec consult_env_config(rebar_state:t(), file:filename()) -> [[tuple()]]. +consult_env_config(State, Filename) -> + RawString = case file:read_file(Filename) of + {error, _} -> "[]."; + {ok, Bin} -> unicode:characters_to_list(Bin) + end, + ReplacedStr = replace_env_vars(RawString), + case rebar_string:consult(unicode:characters_to_list(ReplacedStr)) of + {error, Reason} -> + throw(?PRV_ERROR({bad_term_file, Filename, Reason})); + [Terms] -> + rebar_file_utils:consult_config_terms(State, Terms) + end. + +%% @doc quick and simple variable substitution writeup. +%% Supports `${varname}' but not `$varname' nor nested +%% values such as `${my_${varname}}'. +%% The variable are also defined as only supporting +%% the form `[a-zA-Z_]+[a-zA-Z0-9_]*' as per the POSIX +%% standard. +-spec replace_env_vars(string()) -> unicode:charlist(). +replace_env_vars("") -> ""; +replace_env_vars("${" ++ Str) -> + case until_var_end(Str) of + {ok, VarName, Rest} -> + replace_varname(VarName) ++ replace_env_vars(Rest); + error -> + "${" ++ replace_env_vars(Str) + end; +replace_env_vars([Char|Str]) -> + [Char | replace_env_vars(Str)]. + +until_var_end(Str) -> + case re:run(Str, "([a-zA-Z_]+[a-zA-Z0-9_]*)}", [{capture, [1], list}]) of + nomatch -> + error; + {match, [Name]} -> + {ok, Name, drop_varname(Name, Str)} + end. + +replace_varname(Var) -> + %% os:getenv(Var, "") is only available in OTP-18.0 + case os:getenv(Var) of + false -> ""; + Val -> Val + end. + +drop_varname("", "}" ++ Str) -> Str; +drop_varname([_|Var], [_|Str]) -> drop_varname(Var, Str). diff --git a/src/rebar_string.erl b/src/rebar_string.erl index d03b14e..79867f5 100644 --- a/src/rebar_string.erl +++ b/src/rebar_string.erl @@ -1,7 +1,12 @@ %%% @doc Compatibility module for string functionality %%% for pre- and post-unicode support. +%%% +%%% Also contains other useful string functionality. -module(rebar_string). +%% Compatibility exports -export([join/2, split/2, lexemes/2, trim/3, uppercase/1, lowercase/1, chr/2]). +%% Util exports +-export([consult/1]). -ifdef(unicode_str). @@ -42,3 +47,27 @@ uppercase(Str) -> string:to_upper(Str). lowercase(Str) -> string:to_lower(Str). chr(Str, Char) -> string:chr(Str, Char). -endif. + +%% @doc +%% Given a string or binary, parse it into a list of terms, ala file:consult/1 +-spec consult(unicode:chardata()) -> {error, term()} | [term()]. +consult(Str) -> + consult([], unicode:characters_to_list(Str), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Term} -> consult([], Remaining, [Term | Acc]); + {error, Reason} -> {error, Reason} + end; + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end. diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 929ca47..bc79db0 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -59,7 +59,7 @@ list_templates(State) -> %% Expand a single template's value list_template(Files, {Name, Type, File}, State) -> - case consult(load_file(Files, Type, File)) of + case rebar_string:consult(binary_to_list(load_file(Files, Type, File))) of {error, Reason} -> {error, {consult, File, Reason}}; TemplateTerms -> @@ -158,7 +158,7 @@ drop_var_docs([{K,V}|Rest]) -> [{K,V} | drop_var_docs(Rest)]. %% Load the template index, resolve all variables, and then execute %% the template. create({Template, Type, File}, Files, UserVars, Force, State) -> - TemplateTerms = consult(load_file(Files, Type, File)), + TemplateTerms = rebar_string:consult(binary_to_list(load_file(Files, Type, File))), Vars = drop_var_docs(override_vars(UserVars, get_template_vars(TemplateTerms, State))), maybe_warn_about_name(Vars), TemplateCwd = filename:dirname(File), @@ -394,31 +394,6 @@ load_file(_Files, file, Name) -> {ok, Bin} = file:read_file(Name), Bin. -%% Given a string or binary, parse it into a list of terms, ala file:consult/1 -consult(Str) when is_list(Str) -> - consult([], Str, []); -consult(Bin) when is_binary(Bin)-> - consult([], binary_to_list(Bin), []). - -consult(Cont, Str, Acc) -> - case erl_scan:tokens(Cont, Str, 0) of - {done, Result, Remaining} -> - case Result of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Term} -> consult([], Remaining, [Term | Acc]); - {error, Reason} -> {error, Reason} - end; - {eof, _Other} -> - lists:reverse(Acc); - {error, Info, _} -> - {error, Info} - end; - {more, Cont1} -> - consult(Cont1, eof, Acc) - end. - - write_file(Output, Data, Force) -> %% determine if the target file already exists FileExists = filelib:is_regular(Output), diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl index 8a3362d..f9be2a3 100644 --- a/test/rebar_deps_SUITE.erl +++ b/test/rebar_deps_SUITE.erl @@ -7,7 +7,9 @@ all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, http_os_proxy_settings, https_os_proxy_settings, semver_matching_lt, semver_matching_lte, semver_matching_gt, - valid_version, top_override, {group, git}, {group, pkg}]. + valid_version, top_override, {group, git}, {group, pkg}, + deps_cmd_needs_update_called + ]. groups() -> [{all, [], [flat, pick_highest_left, pick_highest_right, @@ -131,6 +133,8 @@ init_per_testcase(https_os_proxy_settings, Config) -> rebar_test_utils:create_config(GlobalConfigDir, []), rebar_test_utils:init_rebar_state(Config) end; +init_per_testcase(deps_cmd_needs_update_called, Config) -> + rebar_test_utils:init_rebar_state(Config); init_per_testcase(Case, Config) -> {Deps, Warnings, Expect} = deps(Case), Expected = case Expect of @@ -249,6 +253,10 @@ mock_warnings() -> %% the call log. meck:new(rebar_log, [no_link, passthrough]). +mock_rebar_fetch() -> + meck:new(rebar_fetch, [no_link, passthrough]). + + %%% TESTS %%% flat(Config) -> run(Config). pick_highest_left(Config) -> run(Config). @@ -437,7 +445,7 @@ semver_matching_gte(_Config) -> MaxVsn = <<"0.2.0">>, Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>], ?assertEqual({ok, <<"0.2.0">>}, - rebar_packages:cmp_(undefined, MaxVsn, Vsns, + rebar_packages:cmp_(undefined, MaxVsn, Vsns, fun ec_semver:gt/2)). valid_version(_Config) -> @@ -480,6 +488,65 @@ run(Config) -> ), check_warnings(warning_calls(), ?config(warnings, Config), ?config(deps_type, Config)). + +deps_cmd_needs_update_called(Config) -> + mock_rebar_fetch(), + AppDir = ?config(apps, Config), + Deps = rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []} + ,{"b", "1.0.0", [{"c", "1.0.0", []}]} + ,{"c", "2.0.0", []}]), + {SrcDeps, _} = rebar_test_utils:flat_deps(Deps), + mock_git_resource:mock([{deps, SrcDeps}]), + + Name = rebar_test_utils:create_random_name("app_"), + Vsn = rebar_test_utils:create_random_vsn(), + + SubAppsDir = filename:join([AppDir, "apps", Name]), + rebar_test_utils:create_app(SubAppsDir, Name, Vsn, [kernel, stdlib]), + + TopDeps = rebar_test_utils:top_level_deps( + rebar_test_utils:expand_deps(git, [{"b", "1.0.0", []}])), + {ok, RebarConfig} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps}])), + rebar_test_utils:run_and_check(Config, RebarConfig, ["deps"], {ok, []}), + + [<<"b">>] = rebar_fetch_needs_update_calls_sorted(), + + %% Add c to top level + TopDeps2 = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"c", "2.0.0", []} + ,{"b", "1.0.0", []}])), + {ok, RebarConfig2} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps2}])), + rebar_test_utils:run_and_check(Config, RebarConfig2, ["deps"], {ok, []}), + + %% Only top level deps are checked for updates + [<<"b">>, <<"b">>, <<"c">>] = rebar_fetch_needs_update_calls_sorted(), + + %% Lock deps + rebar_test_utils:run_and_check(Config, RebarConfig2, ["lock"], {ok, []}), + NeedsUpdate1 = rebar_fetch_needs_update_calls_sorted(), + + %% Switch c for a as top level deps + TopDeps3 = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []} + ,{"b", "1.0.0", []}])), + + {ok, RebarConfig3} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps3}])), + LockFile = filename:join(AppDir, "rebar.lock"), + RebarConfig4 = rebar_config:merge_locks(RebarConfig3, + rebar_config:consult_lock_file(LockFile)), + + rebar_test_utils:run_and_check(Config, RebarConfig4, ["deps"], {ok, []}), + + NeedsUpdate2 = lists:subtract(rebar_fetch_needs_update_calls_sorted(), NeedsUpdate1), + + %% B and C from lock file + install_deps and A, B and C from 'deps' + [<<"a">>, <<"b">>, <<"b">>, <<"c">>, <<"c">>] = NeedsUpdate2. + + +rebar_fetch_needs_update_calls_sorted() -> + History = meck:history(rebar_fetch), + DepsNames = [rebar_app_info:name(Dep) + || {_, {rebar_fetch, needs_update, [Dep, _]}, _} <- History], + lists:sort(DepsNames). + warning_calls() -> History = meck:history(rebar_log), [{Str, Args} || {_, {rebar_log, log, [warn, Str, Args]}, _} <- History]. diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl index 6579afb..8577a32 100644 --- a/test/rebar_dialyzer_SUITE.erl +++ b/test/rebar_dialyzer_SUITE.erl @@ -15,7 +15,8 @@ update_app_plt/1, build_release_plt/1, plt_apps_option/1, - exclude_and_extra/1]). + exclude_and_extra/1, + cli_args/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -58,7 +59,7 @@ all() -> groups() -> [{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]}, - {build_and_check, [build_release_plt, plt_apps_option, exclude_and_extra]}, + {build_and_check, [cli_args, build_release_plt, plt_apps_option, exclude_and_extra]}, {update, [update_base_plt, update_app_plt]}]. empty_base_plt(Config) -> @@ -309,6 +310,42 @@ exclude_and_extra(Config) -> {ok, PltFiles} = plt_files(Plt), ?assertEqual(Pair, PltFiles). +cli_args(Config) -> + AppDir = ?config(apps, Config), + [{dialyzer, Opts}] = ?config(rebar_config, Config), + BasePlt = ?config(base_plt, Config), + Plt = ?config(plt, Config), + + {value, {_, Prefix}, Opts1} = lists:keytake(plt_prefix, 1, Opts), + {value, {_, BasePrefix}, Opts2} = lists:keytake(base_plt_prefix, 1, Opts1), + {value, {_, Location}, Opts3} = lists:keytake(plt_location, 1, Opts2), + {value, {_, BasePltLocation}, Opts4} = lists:keytake(base_plt_location, 1, Opts3), + RebarConfig = [{dialyzer, Opts4}], + + Name1 = rebar_test_utils:create_random_name("relapp1_"), + Vsn1 = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(filename:join([AppDir,"apps",Name1]), Name1, Vsn1, + [erts]), + Name2 = rebar_test_utils:create_random_name("relapp2_"), + Vsn2 = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(filename:join([AppDir,"apps",Name2]), Name2, Vsn2, + [erts, ec_cnv:to_atom(Name1)]), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer", + "--plt-location=" ++ Location, + "--base-plt-location=" ++ BasePltLocation, + "--plt-prefix=" ++ Prefix, + "--base-plt-prefix=" ++ BasePrefix], + {ok, [{app, Name1}, {app, Name2}]}), + + ErtsFiles = erts_files(), + + {ok, BasePltFiles} = plt_files(BasePlt), + ?assertEqual(ErtsFiles, BasePltFiles), + + {ok, PltFiles} = plt_files(Plt), + ?assertEqual(ErtsFiles, PltFiles). + %% Helpers erts_files() -> |