diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rebar.app.src | 2 | ||||
-rw-r--r-- | src/rebar.hrl | 4 | ||||
-rw-r--r-- | src/rebar_app_info.erl | 11 | ||||
-rw-r--r-- | src/rebar_core.erl | 8 | ||||
-rw-r--r-- | src/rebar_dir.erl | 13 | ||||
-rw-r--r-- | src/rebar_prv_eunit.erl | 220 | ||||
-rw-r--r-- | src/rebar_prv_install_deps.erl | 96 | ||||
-rw-r--r-- | src/rebar_prv_lock.erl | 20 | ||||
-rw-r--r-- | src/rebar_prv_new.erl | 15 | ||||
-rw-r--r-- | src/rebar_prv_release.erl | 2 | ||||
-rw-r--r-- | src/rebar_state.erl | 66 | ||||
-rw-r--r-- | src/rebar_templater.erl | 2 | ||||
-rw-r--r-- | src/rebar_utils.erl | 1 |
13 files changed, 334 insertions, 126 deletions
diff --git a/src/rebar.app.src b/src/rebar.app.src index 62cabf9..ee4bfd7 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "3.0.0-alpha"}, + {vsn, "3.0.0-alpha-2"}, {modules, []}, {registered, []}, {applications, [kernel, diff --git a/src/rebar.hrl b/src/rebar.hrl index 0dfcad0..1f051d7 100644 --- a/src/rebar.hrl +++ b/src/rebar.hrl @@ -14,7 +14,9 @@ -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). -define(DEFAULT_BASE_DIR, "_build"). --define(DEFAULT_PROJECT_APP_DIRS, ["_checkouts", "apps", "lib", "."]). +-define(DEFAULT_ROOT_DIR, "."). +-define(DEFAULT_PROJECT_APP_DIRS, ["apps", "lib", "."]). +-define(DEFAULT_CHECKOUTS_DIR, "_checkouts"). -define(DEFAULT_DEPS_DIR, "lib"). -define(DEFAULT_PLUGINS_DIR, "plugins"). -define(DEFAULT_TEST_DEPS_DIR, "test/lib"). diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 99bc25b..9659c0b 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -34,6 +34,8 @@ source/2, state/1, state/2, + is_checkout/1, + is_checkout/2, valid/1, valid/2]). @@ -53,6 +55,7 @@ out_dir :: file:name(), source :: string() | tuple() | undefined, state :: rebar_state:t() | undefined, + is_checkout=false :: boolean(), valid :: boolean()}). %%============================================================================ @@ -238,6 +241,14 @@ state(AppInfo=#app_info_t{}, State) -> state(#app_info_t{state=State}) -> State. +-spec is_checkout(t(), boolean()) -> t(). +is_checkout(AppInfo=#app_info_t{}, IsCheckout) -> + AppInfo#app_info_t{is_checkout=IsCheckout}. + +-spec is_checkout(t()) -> boolean(). +is_checkout(#app_info_t{is_checkout=IsCheckout}) -> + IsCheckout. + -spec valid(t()) -> boolean(). valid(AppInfo=#app_info_t{valid=undefined}) -> case rebar_app_utils:validate_application_info(AppInfo) of diff --git a/src/rebar_core.erl b/src/rebar_core.erl index db82766..6abab68 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -82,6 +82,8 @@ process_command(State, Command) -> case Command of do -> do(TargetProviders, State); + as -> + do(TargetProviders, State); _ -> Profiles = providers:profiles(CommandProvider), State1 = rebar_state:apply_profiles(State, Profiles), @@ -91,7 +93,11 @@ process_command(State, Command) -> State2 = rebar_state:command_parsed_args(State1, Args), do(TargetProviders, State2); {error, {invalid_option, Option}} -> - {error, io_lib:format("Invalid option ~s on task ~p", [Option, Command])} + {error, io_lib:format("Invalid option ~s on task ~p", [Option, Command])}; + {error, {invalid_option_arg, {Option, Arg}}} -> + {error, io_lib:format("Invalid argument ~s to option ~s", [Arg, Option])}; + {error, {missing_option_arg, Option}} -> + {error, io_lib:format("Missing argument to option ~s", [Option])} end end end. diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index 628ebd3..4a9bf09 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -3,6 +3,8 @@ -export([base_dir/1, deps_dir/1, deps_dir/2, + checkouts_dir/1, + checkouts_dir/2, plugins_dir/1, lib_dirs/1, home_dir/0, @@ -40,6 +42,17 @@ deps_dir(State) -> deps_dir(DepsDir, App) -> filename:join(DepsDir, App). +root_dir(State) -> + rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR). + +-spec checkouts_dir(rebar_state:t()) -> file:filename_all(). +checkouts_dir(State) -> + filename:join(root_dir(State), rebar_state:get(State, checkouts_dir, ?DEFAULT_CHECKOUTS_DIR)). + +-spec checkouts_dir(rebar_state:t(), file:filename_all()) -> file:filename_all(). +checkouts_dir(State, App) -> + filename:join(checkouts_dir(State), App). + -spec plugins_dir(rebar_state:t()) -> file:filename_all(). plugins_dir(State) -> case lists:member(global, rebar_state:current_profiles(State)) of diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0e0e937..6872c99 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -37,13 +37,14 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Performing EUnit tests...", []), - {Opts, _} = rebar_state:command_parsed_args(State), - EUnitOpts = resolve_eunit_opts(State, Opts), - TestApps = filter_checkouts(rebar_state:project_apps(State)), - ok = compile_tests(State, TestApps), - ok = maybe_cover_compile(State, Opts), - AppsToTest = test_dirs(State, TestApps), - Result = eunit:test(AppsToTest, EUnitOpts), + case prepare_tests(State) of + {ok, Tests} -> do_tests(State, Tests); + Error -> Error + end. + +do_tests(State, Tests) -> + EUnitOpts = resolve_eunit_opts(State), + Result = eunit:test(Tests, EUnitOpts), ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER), case handle_results(Result) of {error, Reason} -> @@ -58,45 +59,9 @@ format_error(unknown_error) -> format_error({error_running_tests, Reason}) -> io_lib:format("Error running tests: ~p", [Reason]). -eunit_opts(_State) -> - [{cover, $c, "cover", boolean, help(cover)}, - {verbose, $v, "verbose", boolean, help(verbose)}]. - -help(cover) -> "Generate cover data"; -help(verbose) -> "Verbose output". - -filter_checkouts(Apps) -> filter_checkouts(Apps, []). - -filter_checkouts([], Acc) -> lists:reverse(Acc); -filter_checkouts([App|Rest], Acc) -> - AppDir = filename:absname(rebar_app_info:dir(App)), - CheckoutsDir = filename:absname("_checkouts"), - case lists:prefix(CheckoutsDir, AppDir) of - true -> filter_checkouts(Rest, Acc); - false -> filter_checkouts(Rest, [App|Acc]) - end. - -resolve_eunit_opts(State, Opts) -> - EUnitOpts = rebar_state:get(State, eunit_opts, []), - case proplists:get_value(verbose, Opts, false) of - true -> set_verbose(EUnitOpts); - false -> EUnitOpts - end. - -test_dirs(State, TestApps) -> - %% we need to add "./ebin" if it exists but only if it's not already - %% due to be added - F = fun(App) -> rebar_app_info:dir(App) =/= rebar_dir:get_cwd() end, - BareEbin = filename:join([rebar_dir:base_dir(State), "ebin"]), - case lists:any(F, TestApps) andalso filelib:is_dir(BareEbin) of - false -> application_dirs(TestApps, []); - true -> [{dir, BareEbin}|application_dirs(TestApps, [])] - end. - -application_dirs([], Acc) -> lists:reverse(Acc); -application_dirs([App|Rest], Acc) -> - AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), - application_dirs(Rest, [{application, AppName}|Acc]). +%% =================================================================== +%% Internal functions +%% =================================================================== test_state(State) -> ErlOpts = rebar_state:get(State, eunit_compile_opts, []), @@ -120,14 +85,33 @@ first_files(State) -> EUnitFirst = rebar_state:get(State, eunit_first_files, []), [{erl_first_files, EUnitFirst}]. -set_verbose(Opts) -> - %% if `verbose` is already set don't set it again - case lists:member(verbose, Opts) of - true -> Opts; - false -> [verbose] ++ Opts +prepare_tests(State) -> + {RawOpts, _} = rebar_state:command_parsed_args(State), + resolve_apps(State, RawOpts). + +resolve_apps(State, RawOpts) -> + case proplists:get_value(app, RawOpts) of + undefined -> resolve_suites(State, project_apps(State), RawOpts); + %% convert app name strings to `rebar_app_info` objects + Apps -> AppNames = string:tokens(Apps, [$,]), + ProjectApps = project_apps(State), + case filter_apps_by_name(AppNames, ProjectApps) of + {ok, TestApps} -> resolve_suites(State, TestApps, RawOpts); + Error -> Error + end + end. + +resolve_suites(State, Apps, RawOpts) -> + case proplists:get_value(suite, RawOpts) of + undefined -> compile_tests(State, Apps, all, RawOpts); + Suites -> SuiteNames = string:tokens(Suites, [$,]), + case filter_suites_by_apps(SuiteNames, Apps) of + {ok, S} -> compile_tests(State, Apps, S, RawOpts); + Error -> Error + end end. -compile_tests(State, TestApps) -> +compile_tests(State, TestApps, Suites, RawOpts) -> F = fun(AppInfo) -> AppDir = rebar_app_info:dir(AppInfo), S = case rebar_app_info:state(AppInfo) of @@ -141,24 +125,80 @@ compile_tests(State, TestApps) -> ec_cnv:to_list(rebar_app_info:out_dir(AppInfo))) end, lists:foreach(F, TestApps), - case filelib:is_dir(filename:join([rebar_dir:get_cwd(), "test"])) of - true -> compile_bare_tests(State, TestApps); - false -> ok + ok = maybe_cover_compile(State, RawOpts), + {ok, test_set(TestApps, Suites)}. + +maybe_cover_compile(State, Opts) -> + State1 = case proplists:get_value(cover, Opts, false) of + true -> rebar_state:set(State, cover_enabled, true); + false -> State + end, + rebar_prv_cover:maybe_cover_compile(State1). + +project_apps(State) -> + filter_checkouts(rebar_state:project_apps(State)). + +filter_checkouts(Apps) -> filter_checkouts(Apps, []). + +filter_checkouts([], Acc) -> lists:reverse(Acc); +filter_checkouts([App|Rest], Acc) -> + case rebar_app_info:is_checkout(App) of + true -> filter_checkouts(Rest, Acc); + false -> filter_checkouts(Rest, [App|Acc]) + end. + +%% make sure applications specified actually exist +filter_apps_by_name(AppNames, ProjectApps) -> + filter_apps_by_name(AppNames, ProjectApps, []). + +filter_apps_by_name([], _ProjectApps, Acc) -> {ok, lists:reverse(Acc)}; +filter_apps_by_name([Name|Rest], ProjectApps, Acc) -> + case find_app_by_name(Name, ProjectApps) of + {error, app_not_found} -> + ?PRV_ERROR({error_running_tests, + "Application `" ++ Name ++ "' not found in project."}); + App -> + filter_apps_by_name(Rest, ProjectApps, [App|Acc]) + end. + +find_app_by_name(_, []) -> {error, app_not_found}; +find_app_by_name(Name, [App|Rest]) -> + case Name == binary_to_list(rebar_app_info:name(App)) of + true -> App; + false -> find_app_by_name(Name, Rest) + end. + +%% ensure specified suites are in the applications included +filter_suites_by_apps(Suites, ProjectApps) -> + filter_suites_by_apps(Suites, ProjectApps, []). + +filter_suites_by_apps([], _ProjectApps, Acc) -> {ok, lists:reverse(Acc)}; +filter_suites_by_apps([Suite|Rest], Apps, Acc) -> + Modules = app_modules([binary_to_atom(rebar_app_info:name(A), unicode) || A <- Apps], []), + case lists:member(list_to_atom(Suite), Modules) of + false -> + ?PRV_ERROR({error_running_tests, + "Module `" ++ Suite ++ "' not found in applications."}); + true -> + filter_suites_by_apps(Rest, Apps, [Suite|Acc]) end. -compile_bare_tests(State, TestApps) -> - F = fun(App) -> rebar_app_info:dir(App) == rebar_dir:get_cwd() end, - case lists:filter(F, TestApps) of - %% compile and link just the `test` directory of the base dir - [] -> - Source = filename:join([rebar_dir:get_cwd(), "test"]), - Target = filename:join([rebar_dir:base_dir(State), "test"]), - ok = rebar_file_utils:symlink_or_copy(Source, Target), - rebar_erlc_compiler:compile(replace_src_dirs(State), - rebar_dir:base_dir(State), - filename:join([rebar_dir:base_dir(State), "ebin"])); - %% already compiled `./test` so do nothing - _ -> ok +app_modules([], Acc) -> Acc; +app_modules([App|Rest], Acc) -> + Unload = case application:load(App) of + ok -> true; + {error, {already_loaded, _}} -> false + end, + NewAcc = case application:get_key(App, modules) of + {ok, Modules} -> Modules ++ Acc; + undefined -> Acc + end, + case Unload of + true -> + application:unload(App), + app_modules(Rest, NewAcc); + false -> + app_modules(Rest, NewAcc) end. replace_src_dirs(State) -> @@ -167,15 +207,47 @@ replace_src_dirs(State) -> StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts), rebar_state:set(State, erl_opts, [{src_dirs, ["test"]}|StrippedOpts]). -maybe_cover_compile(State, Opts) -> - State1 = case proplists:get_value(cover, Opts, false) of - true -> rebar_state:set(State, cover_enabled, true); - false -> State - end, - rebar_prv_cover:maybe_cover_compile(State1). +test_set(Apps, all) -> set_apps(Apps, []); +test_set(_Apps, Suites) -> set_suites(Suites, []). + +set_apps([], Acc) -> lists:reverse(Acc); +set_apps([App|Rest], Acc) -> + AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), + set_apps(Rest, [{application, AppName}|Acc]). + +set_suites([], Acc) -> lists:reverse(Acc); +set_suites([Suite|Rest], Acc) -> + set_suites(Rest, [{module, list_to_atom(Suite)}|Acc]). + +resolve_eunit_opts(State) -> + {Opts, _} = rebar_state:command_parsed_args(State), + EUnitOpts = rebar_state:get(State, eunit_opts, []), + case proplists:get_value(verbose, Opts, false) of + true -> set_verbose(EUnitOpts); + false -> EUnitOpts + end. + +set_verbose(Opts) -> + %% if `verbose` is already set don't set it again + case lists:member(verbose, Opts) of + true -> Opts; + false -> [verbose] ++ Opts + end. handle_results(ok) -> ok; handle_results(error) -> {error, unknown_error}; handle_results({error, Reason}) -> {error, {error_running_tests, Reason}}. + +eunit_opts(_State) -> + [{app, undefined, "app", string, help(app)}, + {cover, $c, "cover", boolean, help(cover)}, + {suite, undefined, "suite", string, help(suite)}, + {verbose, $v, "verbose", boolean, help(verbose)}]. + +help(app) -> "List of application test suites to run"; +help(cover) -> "Generate cover data"; +help(suite) -> "List of test suites to run"; +help(verbose) -> "Verbose output". + diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index e4a87ee..e92a3f0 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -174,7 +174,7 @@ compile_order(Source, ProjectApps) -> case rebar_digraph:compile_order(Source) of {ok, Sort} -> %% Valid apps are compiled and good - {ok, lists:dropwhile(fun rebar_app_info:valid/1, Sort -- ProjectApps)}; + {ok, lists:dropwhile(fun not_needs_compile/1, Sort -- ProjectApps)}; {error, Error} -> {error, Error} end. @@ -214,23 +214,28 @@ handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, State) - {[AppInfo | Fetched], NewSeen, NewState}. maybe_lock(Profile, AppInfo, Seen, State, Level) -> - case Profile of - default -> - Name = rebar_app_info:name(AppInfo), - case sets:is_element(Name, Seen) of - false -> - Locks = rebar_state:lock(State), - case lists:any(fun(App) -> rebar_app_info:name(App) =:= Name end, Locks) of - true -> - {sets:add_element(Name, Seen), State}; + case rebar_app_info:is_checkout(AppInfo) of + false -> + case Profile of + default -> + Name = rebar_app_info:name(AppInfo), + case sets:is_element(Name, Seen) of false -> - {sets:add_element(Name, Seen), - rebar_state:lock(State, rebar_app_info:dep_level(AppInfo, Level))} + Locks = rebar_state:lock(State), + case lists:any(fun(App) -> rebar_app_info:name(App) =:= Name end, Locks) of + true -> + {sets:add_element(Name, Seen), State}; + false -> + {sets:add_element(Name, Seen), + rebar_state:lock(State, rebar_app_info:dep_level(AppInfo, Level))} + end; + true -> + {Seen, State} end; - true -> + _ -> {Seen, State} end; - _ -> + true -> {Seen, State} end. @@ -367,7 +372,7 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) -> maybe_fetch(AppInfo, Upgrade, Seen, State) -> AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)), %% Don't fetch dep if it exists in the _checkouts dir - case in_checkouts(AppInfo) of + case rebar_app_info:is_checkout(AppInfo) of true -> false; false -> @@ -392,13 +397,6 @@ maybe_fetch(AppInfo, Upgrade, Seen, State) -> end end. -in_checkouts(AppInfo) -> - Apps = rebar_app_discover:find_apps(["_checkouts"], all), - case rebar_app_utils:find(rebar_app_info:name(AppInfo), Apps) of - {ok, _} -> true; - error -> false - end. - in_default(AppInfo, State) -> Name = ec_cnv:to_list(rebar_app_info:name(AppInfo)), DefaultAppDir = filename:join([rebar_state:get(State, base_dir), "default", "lib", Name]), @@ -427,11 +425,25 @@ parse_deps(DepsDir, Deps, State, Locks, Level) -> end end, {[], []}, Deps). -parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, _DepsDir, _State) when is_list(Vsn) -> - {SrcDepsAcc, [parse_goal(ec_cnv:to_binary(Name) - ,ec_cnv:to_binary(Vsn)) | PkgDepsAcc]}; -parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, _DepsDir, _State) when is_atom(Name) -> - {SrcDepsAcc, [ec_cnv:to_binary(Name) | PkgDepsAcc]}; +parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_list(Vsn) -> + CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), + case rebar_app_info:discover(CheckoutsDir) of + {ok, _App} -> + Dep = new_dep(DepsDir, Name, [], [], State), + {[Dep | SrcDepsAcc], PkgDepsAcc}; + not_found -> + {SrcDepsAcc, [parse_goal(ec_cnv:to_binary(Name) + ,ec_cnv:to_binary(Vsn)) | PkgDepsAcc]} + end; +parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_atom(Name) -> + CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), + case rebar_app_info:discover(CheckoutsDir) of + {ok, _App} -> + Dep = new_dep(DepsDir, Name, [], [], State), + {[Dep | SrcDepsAcc], PkgDepsAcc}; + not_found -> + {SrcDepsAcc, [ec_cnv:to_binary(Name) | PkgDepsAcc]} + end; parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> Dep = new_dep(DepsDir, Name, [], Source, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; @@ -445,8 +457,15 @@ parse_dep({Name, _Vsn, Source, Opts}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) ?WARN("Dependency option list ~p in ~p is not supported and will be ignored", [Opts, Name]), Dep = new_dep(DepsDir, Name, [], Source, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, _, _) when is_integer(Level) -> - {SrcDepsAcc, [{Name, Vsn} | PkgDepsAcc]}; +parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_integer(Level) -> + CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), + case rebar_app_info:discover(CheckoutsDir) of + {ok, _App} -> + Dep = new_dep(DepsDir, Name, [], [], State), + {[Dep | SrcDepsAcc], PkgDepsAcc}; + not_found -> + {SrcDepsAcc, [{Name, Vsn} | PkgDepsAcc]} + end; parse_dep({Name, Source, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) , is_integer(Level) -> Dep = new_dep(DepsDir, Name, [], Source, State), @@ -456,12 +475,19 @@ parse_dep(Dep, _, _, _) -> new_dep(DepsDir, Name, Vsn, Source, State) -> - Dir = ec_cnv:to_list(filename:join(DepsDir, Name)), - {ok, Dep} = case rebar_app_info:discover(Dir) of + CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), + {ok, Dep} = case rebar_app_info:discover(CheckoutsDir) of {ok, App} -> - {ok, App}; + {ok, rebar_app_info:is_checkout(App, true)}; not_found -> - rebar_app_info:new(Name, Vsn, ec_cnv:to_list(filename:join(DepsDir, Name))) + Dir = ec_cnv:to_list(filename:join(DepsDir, Name)), + case rebar_app_info:discover(Dir) of + {ok, App} -> + {ok, App}; + not_found -> + rebar_app_info:new(Name, Vsn, + ec_cnv:to_list(filename:join(DepsDir, Name))) + end end, C = rebar_config:consult(rebar_app_info:dir(Dep)), S = rebar_state:new(rebar_state:new(), C, rebar_app_info:dir(Dep)), @@ -528,3 +554,7 @@ warn_skip_pkg({Name, Source}, State) -> false -> ?WARN(Msg, Args); true -> ?ERROR(Msg, Args), ?FAIL end. + +not_needs_compile(App) -> + not(rebar_app_info:is_checkout(App)) + andalso rebar_app_info:valid(App). diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl index 8a69434..e839168 100644 --- a/src/rebar_prv_lock.erl +++ b/src/rebar_prv_lock.erl @@ -30,16 +30,16 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> AllDeps = rebar_state:lock(State), - Locks = lists:map(fun(Dep) -> - Dir = rebar_app_info:dir(Dep), - Source = rebar_app_info:source(Dep), - - %% If source is tuple it is a source dep - %% e.g. {git, "git://github.com/ninenines/cowboy.git", "master"} - {rebar_app_info:name(Dep) - ,rebar_fetch:lock_source(Dir, Source) - ,rebar_app_info:dep_level(Dep)} - end, AllDeps), + Locks = [begin + Dir = rebar_app_info:dir(Dep), + Source = rebar_app_info:source(Dep), + + %% If source is tuple it is a source dep + %% e.g. {git, "git://github.com/ninenines/cowboy.git", "master"} + {rebar_app_info:name(Dep) + ,rebar_fetch:lock_source(Dir, Source) + ,rebar_app_info:dep_level(Dep)} + end || Dep <- AllDeps, not(rebar_app_info:is_checkout(Dep))], Dir = rebar_state:dir(State), file:write_file(filename:join(Dir, ?LOCK_FILE), io_lib:format("~p.~n", [Locks])), {ok, State}. diff --git a/src/rebar_prv_new.erl b/src/rebar_prv_new.erl index 77e100f..58e27fd 100644 --- a/src/rebar_prv_new.erl +++ b/src/rebar_prv_new.erl @@ -33,15 +33,24 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> case rebar_state:command_args(State) of + ["help"] -> + ?CONSOLE("Call `rebar3 new help <template>` for a detailed description~n", []), + show_short_templates(rebar_templater:list_templates(State)), + {ok, State}; ["help", TemplateName] -> case lists:keyfind(TemplateName, 1, rebar_templater:list_templates(State)) of - false -> io:format("template not found.~n"); + false -> ?CONSOLE("template not found.", []); Term -> show_template(Term) end, {ok, State}; [TemplateName | Opts] -> - Force = is_forced(State), - ok = rebar_templater:new(TemplateName, parse_opts(Opts), Force, State), + case lists:keyfind(TemplateName, 1, rebar_templater:list_templates(State)) of + false -> + ?CONSOLE("template not found.", []); + _ -> + Force = is_forced(State), + ok = rebar_templater:new(TemplateName, parse_opts(Opts), Force, State) + end, {ok, State}; [] -> show_short_templates(rebar_templater:list_templates(State)), diff --git a/src/rebar_prv_release.erl b/src/rebar_prv_release.erl index 766bfe4..db012d5 100644 --- a/src/rebar_prv_release.erl +++ b/src/rebar_prv_release.erl @@ -47,7 +47,7 @@ do(State) -> ,{caller, Caller}], AllOptions); Config -> relx:main([{lib_dirs, LibDirs} - ,{config, Config} + ,{config, lists:reverse(Config)} ,{output_dir, OutputDir} ,{caller, Caller}], AllOptions) end, diff --git a/src/rebar_state.erl b/src/rebar_state.erl index fa0a963..cd127c0 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -240,7 +240,7 @@ merge_opts(NewOpts, OldOpts) -> true -> NewValue; false -> - lists:umerge(lists:sort(NewValue), lists:sort(OldValue)) + tup_umerge(tup_sort(NewValue), tup_sort(OldValue)) end; (_Key, NewValue, _OldValue) -> NewValue @@ -362,3 +362,67 @@ add_hook(pre, {PreHooks, PostHooks}, Hook) -> {[Hook | PreHooks], PostHooks}; add_hook(post, {PreHooks, PostHooks}, Hook) -> {PreHooks, [Hook | PostHooks]}. + + +%% Sort the list in proplist-order, meaning that `{a,b}' and `{a,c}' +%% both compare as usual, and `a' and `b' do the same, but `a' and `{a,b}' will +%% compare based on the first element of the key, and in order. So the following +%% list will sort as: +%% - `[native, {native,o3}, check]' -> `[check, native, {native, o3}]' +%% - `[native, {native,o3}, {native, o2}, check]' -> `[check,native,{native,o3},{native,o2}]' +%% Meaning that: +%% a) no deduplication takes place +%% b) the key of a tuple is what counts in being sorted, but atoms are seen as {atom} +%% as far as comparison is concerned (departing from lists:ukeysort/2) +%% c) order is preserved for similar keys and tuples no matter their size (sort is stable) +%% +%% These properties let us merge proplists fairly easily. +tup_sort(List) -> + lists:sort(fun(A, B) when is_tuple(A), is_tuple(B) -> element(1, A) =< element(1, B) + ; (A, B) when is_tuple(A) -> element(1, A) =< B + ; (A, B) when is_tuple(B) -> A =< element(1, B) + ; (A, B) -> A =< B + end, List). + +%% Custom merge functions. The objective is to behave like lists:umerge/2, +%% except that we also compare the merge elements based on the key if they're a +%% tuple, such that `{key, val1}' is always prioritized over `{key, val0}' if +%% the former is from the 'new' list. +%% +%% This lets us apply proper overrides to list of elements according to profile +%% priority. This function depends on a stable proplist sort. +tup_umerge([], Olds) -> + Olds; +tup_umerge([New|News], Olds) -> + lists:reverse(umerge(News, Olds, [], New)). + +%% This is equivalent to umerge2_2 in the stdlib, except we use the expanded +%% value/key only to compare +umerge(News, [Old|Olds], Merged, Cmp) when element(1, Cmp) == element(1, Old); + element(1, Cmp) == Old; + Cmp == element(1, Old); + Cmp =< Old -> + umerge(News, Olds, [Cmp | Merged], Cmp, Old); +umerge(News, [Old|Olds], Merged, Cmp) -> + umerge(News, Olds, [Old | Merged], Cmp); +umerge(News, [], Merged, Cmp) -> + lists:reverse(News, [Cmp | Merged]). + +%% Similar to stdlib's umerge2_1 in the stdlib, except that when the expanded +%% value/keys compare equal, we check if the element is a full dupe to clear it +%% (like the stdlib function does) or otherwise keep the duplicate around in +%% an order that prioritizes 'New' elements. +umerge([New|News], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp -> + umerge(News, Olds, Merged, New); +umerge([New|News], Olds, Merged, _CmpMerged, Cmp) when element(1,New) == element(1, Cmp); + element(1,New) == Cmp; + New == element(1, Cmp); + New =< Cmp -> + umerge(News, Olds, [New | Merged], New, Cmp); +umerge([New|News], Olds, Merged, _CmpMerged, Cmp) -> % > + umerge(News, Olds, [Cmp | Merged], New); +umerge([], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp -> + lists:reverse(Olds, Merged); +umerge([], Olds, Merged, _CmpMerged, Cmp) -> + lists:reverse(Olds, [Cmp | Merged]). + diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 143c28b..75cbb87 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -156,7 +156,7 @@ default_author_and_email() -> %% Ok, try mecurial case rebar_utils:sh("hg showconfig ui.username", [return_on_error]) of {ok, NameEmail} -> - case re:run(NameEmail, [{capture, [1,2], list}]) of + case re:run(NameEmail, "^(.*) <(.*)>$", [{capture, [1,2], list}]) of {match, [Name, Email]} -> {Name, Email}; _ -> diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 2f27fd3..df25997 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -387,6 +387,7 @@ beams(Dir) -> -spec abort() -> no_return(). abort() -> throw(rebar_abort). + -spec abort(string(), [term()]) -> no_return(). abort(String, Args) -> ?ERROR(String, Args), |