diff options
Diffstat (limited to 'src')
50 files changed, 1631 insertions, 677 deletions
diff --git a/src/r3.erl b/src/r3.erl new file mode 100644 index 0000000..5e8b26d --- /dev/null +++ b/src/r3.erl @@ -0,0 +1,7 @@ +%%% external alias for rebar_agent +-module(r3). +-export([do/1, do/2]). + +do(Command) -> rebar_agent:do(Command). + +do(Namespace, Command) -> rebar_agent:do(Namespace, Command). diff --git a/src/rebar.app.src b/src/rebar.app.src index f753784..e5d56bb 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-5"}, + {vsn, "3.0.0-beta-1"}, {modules, []}, {registered, []}, {applications, [kernel, @@ -17,6 +17,7 @@ common_test, erlware_commons, providers, + bbmustache, relx, inets]}, {env, [ @@ -44,7 +45,10 @@ rebar_prv_lock, rebar_prv_new, rebar_prv_packages, + rebar_prv_plugins, + rebar_prv_plugins_upgrade, rebar_prv_release, + rebar_prv_relup, rebar_prv_report, rebar_prv_shell, rebar_prv_tar, diff --git a/src/rebar3.erl b/src/rebar3.erl index 1a02407..c501709 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -56,7 +56,7 @@ main(Args) -> %% Erlang-API entry point run(BaseState, Commands) -> - _ = application:load(rebar), + start_and_load_apps(), BaseState1 = rebar_state:set(BaseState, task, Commands), BaseState2 = rebar_state:set(BaseState1, caller, api), run_aux(BaseState2, Commands). @@ -66,7 +66,7 @@ run(BaseState, Commands) -> %% ==================================================================== run(RawArgs) -> - _ = application:load(rebar), + start_and_load_apps(), BaseState = init_config(), BaseState1 = rebar_state:set(BaseState, caller, command_line), @@ -83,16 +83,6 @@ run(RawArgs) -> run_aux(BaseState2, RawArgs). run_aux(State, RawArgs) -> - %% Make sure crypto is running - case crypto:start() of - ok -> ok; - {error,{already_started,crypto}} -> ok - end, - application:start(asn1), - application:start(public_key), - application:start(ssl), - inets:start(), - State2 = case os:getenv("REBAR_PROFILE") of false -> State; @@ -105,13 +95,12 @@ run_aux(State, RawArgs) -> %% Process each command, resetting any state between each one BaseDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), State3 = rebar_state:set(State2, base_dir, - filename:join(filename:absname(rebar_state:dir(State2)), BaseDir)), + filename:join(filename:absname(rebar_state:dir(State2)), BaseDir)), {ok, Providers} = application:get_env(rebar, providers), - State4 = rebar_plugins:install(State3), - %% Providers can modify profiles stored in opts, so set default after initializing providers - State5 = rebar_state:create_logic_providers(Providers, State4), + State4 = rebar_state:create_logic_providers(Providers, State3), + State5 = rebar_plugins:project_apps_install(State4), State6 = rebar_state:default(State5, rebar_state:opts(State5)), {Task, Args} = parse_args(RawArgs), @@ -131,8 +120,7 @@ init_config() -> ConfigFile -> rebar_config:consult_file(ConfigFile) end, - - Config1 = rebar_config:merge_locks(Config, rebar_config:consult_file(?LOCK_FILE)), + Config1 = rebar_config:merge_locks(Config, rebar_config:consult_lock_file(?LOCK_FILE)), %% If $HOME/.config/rebar3/config exists load and use as global config GlobalConfigFile = rebar_dir:global_config(), @@ -140,8 +128,19 @@ init_config() -> true -> ?DEBUG("Load global config file ~p", [GlobalConfigFile]), - GlobalConfig = rebar_state:new(rebar_config:consult_file(GlobalConfigFile)), - rebar_state:new(GlobalConfig, Config1); + GlobalConfigTerms = rebar_config:consult_file(GlobalConfigFile), + GlobalConfig = rebar_state:new(GlobalConfigTerms), + + %% We don't want to worry about global plugin install state effecting later + %% usage. So we throw away the global profile state used for plugin install. + GlobalConfigThrowAway = rebar_state:current_profiles(GlobalConfig, [global]), + GlobalState = rebar_plugins:handle_plugins(global, + rebar_state:get(GlobalConfigThrowAway, plugins, []), + GlobalConfigThrowAway), + GlobalPlugins = rebar_state:providers(GlobalState), + GlobalConfig2 = rebar_state:set(GlobalConfig, plugins, []), + GlobalConfig3 = rebar_state:set(GlobalConfig2, {plugins, global}, rebar_state:get(GlobalConfigThrowAway, plugins, [])), + rebar_state:providers(rebar_state:new(GlobalConfig3, Config1), GlobalPlugins); false -> rebar_state:new(Config1) end, @@ -255,5 +254,32 @@ handle_error(Error) -> %% Dump this error to console ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace", []), ?DEBUG("Uncaught error: ~p", [Error]), + case erlang:get_stacktrace() of + [] -> ok; + Trace -> + ?DEBUG("Stack trace to the error location: ~p", [Trace]) + end, ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []), erlang:halt(1). + +start_and_load_apps() -> + _ = application:load(rebar), + %% Make sure crypto is running + case crypto:start() of + ok -> ok; + {error,{already_started,crypto}} -> ok + end, + application:start(asn1), + application:start(public_key), + application:start(ssl), + inets:start(), + inets:start(httpc, [{profile, hex}]), + http_opts(). + +http_opts() -> + Opts = [{max_sessions, 4}, + {max_keep_alive_length, 4}, + {keep_alive_timeout, 120000}, + {max_pipeline_length, 4}, + {pipeline_timeout, 60000}], + httpc:set_options(Opts, hex). diff --git a/src/rebar_agent.erl b/src/rebar_agent.erl new file mode 100644 index 0000000..24ac626 --- /dev/null +++ b/src/rebar_agent.erl @@ -0,0 +1,118 @@ +-module(rebar_agent). +-export([start_link/1, do/1, do/2]). +-export([init/1, + handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +-include("rebar.hrl"). + +-record(state, {state, + cwd, + show_warning=true}). + +start_link(State) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, State, []). + +do(Command) when is_atom(Command) -> + gen_server:call(?MODULE, {cmd, Command}, infinity). + +do(Namespace, Command) when is_atom(Namespace), is_atom(Command) -> + gen_server:call(?MODULE, {cmd, Namespace, Command}, infinity). + +init(State) -> + Cwd = rebar_dir:get_cwd(), + {ok, #state{state=State, cwd=Cwd}}. + +handle_call({cmd, Command}, _From, State=#state{state=RState, cwd=Cwd}) -> + MidState = maybe_show_warning(State), + {Res, NewRState} = run(default, Command, RState, Cwd), + {reply, Res, MidState#state{state=NewRState}}; +handle_call({cmd, Namespace, Command}, _From, State = #state{state=RState, cwd=Cwd}) -> + MidState = maybe_show_warning(State), + {Res, NewRState} = run(Namespace, Command, RState, Cwd), + {reply, Res, MidState#state{state=NewRState}}; +handle_call(_Call, _From, State) -> + {noreply, State}. + +handle_cast(_Cast, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +run(Namespace, Command, RState, Cwd) -> + try + case rebar_dir:get_cwd() of + Cwd -> + Args = [atom_to_list(Namespace), atom_to_list(Command)], + CmdState0 = refresh_state(RState, Cwd), + CmdState1 = rebar_state:set(CmdState0, task, atom_to_list(Command)), + CmdState = rebar_state:set(CmdState1, caller, api), + case rebar3:run(CmdState, Args) of + {ok, TmpState} -> + refresh_paths(TmpState), + {ok, CmdState}; + {error, Err} when is_list(Err) -> + refresh_paths(CmdState), + {{error, lists:flatten(Err)}, CmdState}; + {error, Err} -> + refresh_paths(CmdState), + {{error, Err}, CmdState} + end; + _ -> + {{error, cwd_changed}, RState} + end + catch + Type:Reason -> + ?DEBUG("Agent Stacktrace: ~p", [erlang:get_stacktrace()]), + {{error, {Type, Reason}}, RState} + end. + +maybe_show_warning(S=#state{show_warning=true}) -> + ?WARN("This feature is experimental and may be modified or removed at any time.", []), + S#state{show_warning=false}; +maybe_show_warning(State) -> + State. + +refresh_paths(RState) -> + ToRefresh = (rebar_state:code_paths(RState, all_deps) + ++ [filename:join([rebar_app_info:out_dir(App), "test"]) + || App <- rebar_state:project_apps(RState)] + %% make sure to never reload self; halt()s the VM + ) -- [filename:dirname(code:which(?MODULE))], + %% Similar to rebar_utils:update_code/1, but also forces a reload + %% of used modules. Also forces to reload all of ebin/ instead + %% of just the modules in the .app file, because 'extra_src_dirs' + %% allows to load and compile files that are not to be kept + %% in the app file. + lists:foreach(fun(Path) -> + Name = filename:basename(Path, "/ebin"), + Files = filelib:wildcard(filename:join([Path, "*.beam"])), + Modules = [list_to_atom(filename:basename(F, ".beam")) + || F <- Files], + App = list_to_atom(Name), + application:load(App), + case application:get_key(App, modules) of + undefined -> + code:add_patha(Path), + ok; + {ok, _} -> + ?DEBUG("reloading ~p from ~s", [Modules, Path]), + code:replace_path(Name, Path), + [begin code:purge(M), code:delete(M), code:load_file(M) end + || M <- Modules] + end + end, ToRefresh). + +refresh_state(RState, _Dir) -> + lists:foldl( + fun(F, State) -> F(State) end, + rebar3:init_config(), + [fun(S) -> rebar_state:apply_profiles(S, rebar_state:current_profiles(RState)) end] + ). diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl index 73401bc..9c4a5ff 100644 --- a/src/rebar_app_discover.erl +++ b/src/rebar_app_discover.erl @@ -7,6 +7,7 @@ find_apps/2, find_app/2]). +-include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). do(State, LibDirs) -> @@ -19,13 +20,19 @@ do(State, LibDirs) -> %% Sort apps so we get the same merged deps config everytime SortedApps = rebar_utils:sort_deps(Apps), lists:foldl(fun(AppInfo, StateAcc) -> - {AppInfo1, StateAcc1} = merge_deps(AppInfo, StateAcc), Name = rebar_app_info:name(AppInfo), - OutDir = filename:join(DepsDir, Name), - AppInfo2 = rebar_app_info:out_dir(AppInfo1, OutDir), - ProjectDeps1 = lists:delete(Name, ProjectDeps), - rebar_state:project_apps(StateAcc1 - ,rebar_app_info:deps(AppInfo2, ProjectDeps1)) + case enable(State, AppInfo) of + true -> + {AppInfo1, StateAcc1} = merge_deps(AppInfo, StateAcc), + OutDir = filename:join(DepsDir, Name), + AppInfo2 = rebar_app_info:out_dir(AppInfo1, OutDir), + ProjectDeps1 = lists:delete(Name, ProjectDeps), + rebar_state:project_apps(StateAcc1 + ,rebar_app_info:deps(AppInfo2, ProjectDeps1)); + false -> + ?INFO("Ignoring ~s", [Name]), + StateAcc + end end, State, SortedApps). format_error({module_list, File}) -> @@ -44,7 +51,8 @@ merge_deps(AppInfo, State) -> rebar_state:apply_profiles( rebar_state:new(reset_hooks(rebar_state:opts(State, Default)), C, rebar_app_info:dir(AppInfo)), CurrentProfiles), Name), - AppInfo1 = rebar_app_info:state(AppInfo, AppState), + AppState1 = rebar_state:set(AppState, artifacts, []), + AppInfo1 = rebar_app_info:state(AppInfo, AppState1), State1 = lists:foldl(fun(Profile, StateAcc) -> AppProfDeps = rebar_state:get(AppState, {deps, Profile}, []), @@ -65,10 +73,10 @@ project_app_config(AppInfo, State) -> %% Here we check if the app is at the root of the project. %% If it is, then drop the hooks from the config so they aren't run twice maybe_reset_hooks(C, Dir, State) -> - case filename:dirname(rebar_dir:root_dir(State)) of + case ec_file:real_dir_path(rebar_dir:root_dir(State)) of Dir -> C1 = proplists:delete(provider_hooks, C), - proplists:delete(hooks, C1); + proplists:delete(post_hooks, proplists:delete(pre_hooks, C1)); _ -> C end. @@ -123,9 +131,42 @@ find_apps(LibDirs, Validate) -> find_app(AppDir, Validate) -> AppFile = filelib:wildcard(filename:join([AppDir, "ebin", "*.app"])), AppSrcFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src"])), - case AppFile of - [File] -> - AppInfo = create_app_info(AppDir, File), + AppInfo = try_handle_app_file(AppFile, AppDir, AppSrcFile, Validate), + AppInfo. + +app_dir(AppFile) -> + filename:join(rebar_utils:droplast(filename:split(filename:dirname(AppFile)))). + +-spec create_app_info(file:name(), file:name()) -> rebar_app_info:t() | {error, term()}. +create_app_info(AppDir, AppFile) -> + [{application, AppName, AppDetails}] = rebar_config:consult_app_file(AppFile), + AppVsn = proplists:get_value(vsn, AppDetails), + Applications = proplists:get_value(applications, AppDetails, []), + IncludedApplications = proplists:get_value(included_applications, AppDetails, []), + {ok, AppInfo} = rebar_app_info:new(AppName, AppVsn, AppDir, []), + AppInfo1 = rebar_app_info:applications( + rebar_app_info:app_details(AppInfo, AppDetails), + IncludedApplications++Applications), + Valid = case rebar_app_utils:validate_application_info(AppInfo1) of + true -> + true; + _ -> + false + end, + rebar_app_info:dir(rebar_app_info:valid(AppInfo1, Valid), AppDir). + +dedup([]) -> []; +dedup([A]) -> [A]; +dedup([H,H|T]) -> dedup([H|T]); +dedup([H|T]) -> [H|dedup(T)]. + +%% Read in and parse the .app file if it is availabe. Do the same for +%% the .app.src file if it exists. +try_handle_app_file([], AppDir, AppSrcFile, Validate) -> + try_handle_app_src_file([], AppDir, AppSrcFile, Validate); +try_handle_app_file([File], AppDir, AppSrcFile, Validate) -> + try create_app_info(AppDir, File) of + AppInfo -> AppInfo1 = rebar_app_info:app_file(AppInfo, File), AppInfo2 = case AppSrcFile of [F] -> @@ -134,7 +175,7 @@ find_app(AppDir, Validate) -> AppInfo1; Other when is_list(Other) -> throw({error, {multiple_app_files, Other}}) - end, + end, case Validate of valid -> case rebar_app_utils:validate_application_info(AppInfo2) of @@ -152,57 +193,35 @@ find_app(AppDir, Validate) -> end; all -> {true, AppInfo2} - end; - [] -> - case AppSrcFile of - [File] -> - case Validate of - V when V =:= invalid ; V =:= all -> - AppInfo = create_app_info(AppDir, File), - case AppInfo of - {error, Reason} -> - throw({error, {invalid_app_file, File, Reason}}); - _ -> - {true, rebar_app_info:app_file_src(AppInfo, File)} - end; - valid -> - false - end; - [] -> - false; - Other when is_list(Other) -> - throw({error, {multiple_app_files, Other}}) - end; - Other when is_list(Other) -> - throw({error, {multiple_app_files, Other}}) - end. - -app_dir(AppFile) -> - filename:join(rebar_utils:droplast(filename:split(filename:dirname(AppFile)))). - --spec create_app_info(file:name(), file:name()) -> rebar_app_info:t() | {error, term()}. -create_app_info(AppDir, AppFile) -> - case file:consult(AppFile) of - {ok, [{application, AppName, AppDetails}]} -> - AppVsn = proplists:get_value(vsn, AppDetails), - Applications = proplists:get_value(applications, AppDetails, []), - IncludedApplications = proplists:get_value(included_applications, AppDetails, []), - {ok, AppInfo} = rebar_app_info:new(AppName, AppVsn, AppDir, []), - AppInfo1 = rebar_app_info:applications( - rebar_app_info:app_details(AppInfo, AppDetails), - IncludedApplications++Applications), - Valid = case rebar_app_utils:validate_application_info(AppInfo1) of - true -> - true; - _ -> - false - end, - rebar_app_info:dir(rebar_app_info:valid(AppInfo1, Valid), AppDir); + end + catch + throw:{error, {Module, Reason}} -> + ?DEBUG("Falling back to app.src file because .app failed: ~s", [Module:format_error(Reason)]), + try_handle_app_src_file(File, AppDir, AppSrcFile, Validate) + end; +try_handle_app_file(Other, _AppDir, _AppSrcFile, _Validate) -> + throw({error, {multiple_app_files, Other}}). + +%% Read in the .app.src file if we aren't looking for a valid (already built) app +try_handle_app_src_file(_, _AppDir, [], _Validate) -> + false; +try_handle_app_src_file(_, _AppDir, _AppSrcFile, valid) -> + false; +try_handle_app_src_file(_, AppDir, [File], Validate) when Validate =:= invalid + ; Validate =:= all -> + AppInfo = create_app_info(AppDir, File), + case AppInfo of {error, Reason} -> - {error, Reason} - end. + throw({error, {invalid_app_file, File, Reason}}); + _ -> + {true, rebar_app_info:app_file_src(AppInfo, File)} + end; +try_handle_app_src_file(_, _AppDir, Other, _Validate) -> + throw({error, {multiple_app_files, Other}}). -dedup([]) -> []; -dedup([A]) -> [A]; -dedup([H,H|T]) -> dedup([H|T]); -dedup([H|T]) -> [H|dedup(T)]. +enable(State, AppInfo) -> + not lists:member(to_atom(rebar_app_info:name(AppInfo)), + rebar_state:get(State, excluded_apps, [])). + +to_atom(Bin) -> + list_to_atom(binary_to_list(Bin)). diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 91640f2..6e35b8f 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -34,29 +34,35 @@ source/2, state/1, state/2, + state_or_new/2, + is_lock/1, + is_lock/2, is_checkout/1, is_checkout/2, valid/1, valid/2]). +-include("rebar.hrl"). + -export_type([t/0]). --record(app_info_t, {name :: binary(), - app_file_src :: file:filename_all() | undefined, - app_file :: file:filename_all() | undefined, - config :: rebar_state:t() | undefined, - original_vsn :: binary() | string() | undefined, - app_details=[] :: list(), - applications=[] :: list(), - deps=[] :: list(), +-record(app_info_t, {name :: binary(), + app_file_src :: file:filename_all() | undefined, + app_file :: file:filename_all() | undefined, + config :: rebar_state:t() | undefined, + original_vsn :: binary() | string() | undefined, + app_details=[] :: list(), + applications=[] :: list(), + deps=[] :: list(), profiles=[default] :: [atom()], - dep_level=0 :: integer(), - dir :: file:name(), - out_dir :: file:name(), - source :: string() | tuple() | undefined, - state :: rebar_state:t() | undefined, - is_checkout=false :: boolean(), - valid :: boolean()}). + dep_level=0 :: integer(), + dir :: file:name(), + out_dir :: file:name(), + source :: string() | tuple() | undefined, + state :: rebar_state:t() | undefined, + is_lock=false :: boolean(), + is_checkout=false :: boolean(), + valid :: boolean()}). %%============================================================================ %% types @@ -158,17 +164,18 @@ app_file(AppInfo=#app_info_t{}, AppFile) -> -spec app_details(t()) -> list(). app_details(AppInfo=#app_info_t{app_details=[]}) -> - AppFile = case app_file(AppInfo) of - undefined -> - app_file_src(AppInfo); - File -> - File - end, - case file:consult(AppFile) of - {ok, [{application, _, AppDetails}]} -> - AppDetails; - _ -> - [] + case app_file(AppInfo) of + undefined -> + rebar_file_utils:try_consult(app_file_src(AppInfo)); + AppFile -> + try + rebar_file_utils:try_consult(AppFile) + catch + throw:{error, {Module, Reason}} -> + ?DEBUG("Warning, falling back to .app.src because of: ~s", + [Module:format_error(Reason)]), + rebar_file_utils:try_consult(app_file_src(AppInfo)) + end end; app_details(#app_info_t{app_details=AppDetails}) -> AppDetails. @@ -254,6 +261,22 @@ state(AppInfo=#app_info_t{}, State) -> state(#app_info_t{state=State}) -> State. +-spec state_or_new(rebar_state:t(), t()) -> rebar_state:t(). +state_or_new(State, AppInfo=#app_info_t{state=undefined}) -> + AppDir = dir(AppInfo), + C = rebar_config:consult(AppDir), + rebar_state:new(State, C, AppDir); +state_or_new(_State, #app_info_t{state=State}) -> + State. + +-spec is_lock(t(), boolean()) -> t(). +is_lock(AppInfo=#app_info_t{}, IsLock) -> + AppInfo#app_info_t{is_lock=IsLock}. + +-spec is_lock(t()) -> boolean(). +is_lock(#app_info_t{is_lock=IsLock}) -> + IsLock. + -spec is_checkout(t(), boolean()) -> t(). is_checkout(AppInfo=#app_info_t{}, IsCheckout) -> AppInfo#app_info_t{is_checkout=IsCheckout}. @@ -263,8 +286,9 @@ 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 +valid(AppInfo=#app_info_t{valid=undefined, state=State}) -> + case rebar_app_utils:validate_application_info(AppInfo) + andalso rebar_state:has_all_artifacts(State) =:= true of true -> true; _ -> diff --git a/src/rebar_config.erl b/src/rebar_config.erl index c858fef..3e06c38 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -27,7 +27,10 @@ -module(rebar_config). -export([consult/1 + ,consult_app_file/1 ,consult_file/1 + ,consult_lock_file/1 + ,verify_config_format/1 ,format_error/1 ,merge_locks/2]). @@ -43,10 +46,21 @@ consult(Dir) -> consult_file(filename:join(Dir, ?DEFAULT_CONFIG_FILE)). --spec consult_file(file:name()) -> [any()]. -consult_file(File) when is_binary(File) -> - consult_file(binary_to_list(File)); +consult_app_file(File) -> + consult_file_(File). + +consult_lock_file(File) -> + consult_file_(File). + consult_file(File) -> + Terms = consult_file_(File), + true = verify_config_format(Terms), + Terms. + +-spec consult_file_(file:name()) -> [any()]. +consult_file_(File) when is_binary(File) -> + consult_file_(binary_to_list(File)); +consult_file_(File) -> case filename:extension(File) of ".script" -> consult_and_eval(remove_script_ext(File), File); @@ -57,10 +71,17 @@ consult_file(File) -> {ok, Terms} = consult_and_eval(File, Script), Terms; false -> - try_consult(File) + rebar_file_utils:try_consult(File) end end. +verify_config_format([]) -> + true; +verify_config_format([{_Key, _Value} | T]) -> + verify_config_format(T); +verify_config_format([Term | _]) -> + throw(?PRV_ERROR({bad_config_format, Term})). + %% no lockfile merge_locks(Config, []) -> Config; @@ -78,6 +99,8 @@ merge_locks(Config, [Locks]) -> NewDeps = find_newly_added(ConfigDeps, Locks), [{{locks, default}, Locks}, {{deps, default}, NewDeps++Deps} | Config]. +format_error({bad_config_format, Term}) -> + io_lib:format("Unable to parse config. Term is not in {Key, Value} format:~n~p", [Term]); format_error({bad_dep_name, Dep}) -> io_lib:format("Dependency name must be an atom, instead found: ~p", [Dep]). @@ -87,22 +110,12 @@ format_error({bad_dep_name, Dep}) -> consult_and_eval(File, Script) -> ?DEBUG("Evaluating config script ~p", [Script]), - StateData = try_consult(File), + StateData = rebar_file_utils:try_consult(File), file:script(Script, bs([{'CONFIG', StateData}, {'SCRIPT', Script}])). remove_script_ext(F) -> filename:rootname(F, ".script"). -try_consult(File) -> - case file:consult(File) of - {ok, Terms} -> - Terms; - {error, enoent} -> - []; - {error, Reason} -> - ?ABORT("Failed to read config file ~s:~n ~p", [File, Reason]) - end. - bs(Vars) -> lists:foldl(fun({K,V}, Bs) -> erl_eval:add_binding(K, V, Bs) diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 7fe7332..b1647f0 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -26,9 +26,10 @@ %% ------------------------------------------------------------------- -module(rebar_core). --export([init_command/2, process_namespace/2, process_command/2, do/2]). +-export([init_command/2, process_namespace/2, process_command/2, do/2, format_error/1]). -include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). init_command(State, do) -> process_command(rebar_state:namespace(State, default), do); @@ -119,9 +120,22 @@ do([ProviderName | Rest], State) -> ,rebar_state:providers(State) ,rebar_state:namespace(State)) end, - case providers:do(Provider, State) of + + try providers:do(Provider, State) of {ok, State1} -> do(Rest, State1); {error, Error} -> {error, Error} + catch + error:undef -> + %% This should really only happen if a plugin provider doesn't export do/1 + ?DEBUG("Undefined call to provider's do function:~n~p", [erlang:get_stacktrace()]), + ?PRV_ERROR({bad_provider_namespace, ProviderName}); + error:{badrecord,provider} -> + {error, ProviderName} end. + +format_error({bad_provider_namespace, {Namespace, Name}}) -> + io_lib:format("Undefined command ~s in namespace ~s", [Name, Namespace]); +format_error({bad_provider_namespace, Name}) -> + io_lib:format("Undefined command ~s", [Name]). diff --git a/src/rebar_digraph.erl b/src/rebar_digraph.erl index 9e10d49..d52a811 100644 --- a/src/rebar_digraph.erl +++ b/src/rebar_digraph.erl @@ -124,6 +124,10 @@ find_app_by_name(Name, Apps) -> rebar_app_info:name(App) =:= Name end, Apps). +%% The union of all entries in the applications list for an app and +%% the deps listed in its rebar.config is all deps that may be needed +%% for building the app. all_apps_deps(App) -> - Applications = [atom_to_binary(X, utf8) || X <- rebar_app_info:applications(App)], - lists:usort(rebar_app_info:deps(App) ++ Applications). + Applications = lists:usort([atom_to_binary(X, utf8) || X <- rebar_app_info:applications(App)]), + Deps = lists:usort(lists:map(fun({Name, _}) -> Name; (Name) -> Name end, rebar_app_info:deps(App))), + lists:umerge(Deps, Applications). diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index a94c72d..7af94ea 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -20,7 +20,10 @@ template_dir/1, processing_base_dir/1, processing_base_dir/2, - make_relative_path/2]). + make_relative_path/2, + src_dirs/1, src_dirs/2, + extra_src_dirs/1, extra_src_dirs/2, + all_src_dirs/1, all_src_dirs/3]). -include("rebar.hrl"). @@ -30,15 +33,16 @@ base_dir(State) -> -spec profile_dir(rebar_state:t(), [atom()]) -> file:filename_all(). profile_dir(State, Profiles) -> - ProfilesStrings = case [ec_cnv:to_list(P) || P <- Profiles] of - ["default"] -> ["default"]; + {BaseDir, ProfilesStrings} = case [ec_cnv:to_list(P) || P <- Profiles] of + ["global" | _] -> {?MODULE:global_cache_dir(State), [""]}; + ["bootstrap", "default"] -> {rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), ["default"]}; + ["default"] -> {rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), ["default"]}; %% drop `default` from the profile dir if it's implicit and reverse order %% of profiles to match order passed to `as` - ["default"|Rest] -> Rest + ["default"|Rest] -> {rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), Rest} end, ProfilesDir = string:join(ProfilesStrings, "+"), - filename:join(rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), ProfilesDir). - + filename:join(BaseDir, ProfilesDir). -spec deps_dir(rebar_state:t()) -> file:filename_all(). deps_dir(State) -> @@ -81,11 +85,11 @@ global_config_dir(State) -> rebar_state:get(State, global_rebar_dir, filename:join([Home, ".config", "rebar3"])). global_config(State) -> - filename:join(global_config_dir(State), "config"). + filename:join(global_config_dir(State), "rebar.config"). global_config() -> Home = home_dir(), - filename:join([Home, ".config", "rebar3", "config"]). + filename:join([Home, ".config", "rebar3", "rebar.config"]). global_cache_dir(State) -> Home = home_dir(), @@ -96,7 +100,11 @@ local_cache_dir(Dir) -> get_cwd() -> {ok, Dir} = file:get_cwd(), - Dir. + %% On windows cwd may return capital letter for drive, + %% for example C:/foobar. But as said in http://www.erlang.org/doc/man/filename.html#join-1 + %% filename:join/1,2 anyway will convert drive-letter to lowercase, so we have to "internalize" + %% cwd as soon as it possible. + filename:join([Dir]). template_globals(State) -> filename:join([global_config_dir(State), "templates", "globals"]). @@ -120,3 +128,35 @@ do_make_relative_path([H|T1], [H|T2]) -> do_make_relative_path(Source, Target) -> Base = lists:duplicate(max(length(Target) - 1, 0), ".."), filename:join(Base ++ Source). + +-spec src_dirs(rebar_state:t()) -> list(file:filename_all()). +src_dirs(State) -> src_dirs(State, []). + +-spec src_dirs(rebar_state:t(), list(file:filename_all())) -> list(file:filename_all()). +src_dirs(State, Default) -> + ErlOpts = rebar_utils:erl_opts(State), + Vs = proplists:get_all_values(src_dirs, ErlOpts), + case lists:append([rebar_state:get(State, src_dirs, []) | Vs]) of + [] -> Default; + Dirs -> Dirs + end. + +-spec extra_src_dirs(rebar_state:t()) -> list(file:filename_all()). +extra_src_dirs(State) -> extra_src_dirs(State, []). + +-spec extra_src_dirs(rebar_state:t(), list(file:filename_all())) -> list(file:filename_all()). +extra_src_dirs(State, Default) -> + ErlOpts = rebar_utils:erl_opts(State), + Vs = proplists:get_all_values(extra_src_dirs, ErlOpts), + case lists:append([rebar_state:get(State, extra_src_dirs, []) | Vs]) of + [] -> Default; + Dirs -> Dirs + end. + +-spec all_src_dirs(rebar_state:t()) -> list(file:filename_all()). +all_src_dirs(State) -> all_src_dirs(State, [], []). + +-spec all_src_dirs(rebar_state:t(), list(file:filename_all()), list(file:filename_all())) -> + list(file:filename_all()). +all_src_dirs(State, SrcDefault, ExtraDefault) -> + src_dirs(State, SrcDefault) ++ extra_src_dirs(State, ExtraDefault). diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index c7a3474..b9072a3 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -143,8 +143,7 @@ doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) -> %% Support the src_dirs option allowing multiple directories to %% contain erlang source. This might be used, for example, should %% eunit tests be separated from the core application source. - SrcDirs = [filename:join(Dir, X) || X <- proplists:get_value(src_dirs, ErlOpts, ["src"]) ++ - proplists:get_value(extra_src_dirs, ErlOpts, [])], + SrcDirs = [filename:join(Dir, X) || X <- rebar_dir:all_src_dirs(Config, ["src"], [])], AllErlFiles = gather_src(SrcDirs, []) ++ MoreSources, %% Make sure that ebin/ exists and is on the path diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl index 0aca308..64c5380 100644 --- a/src/rebar_fetch.erl +++ b/src/rebar_fetch.erl @@ -72,7 +72,7 @@ format_error({failed_extract, CachePath}) -> format_error({bad_etag, Source}) -> io_lib:format("MD5 Checksum comparison failed for: ~s", [Source]); format_error({fetch_fail, Source}) -> - io_lib:format("Failed to fetch and copy dep: ~s", [Source]); + io_lib:format("Failed to fetch and copy dep: ~p", [Source]); format_error({bad_checksum, File}) -> io_lib:format("Checksum mismatch against tarball in ~s", [File]); format_error({bad_registry_checksum, File}) -> diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl index ef2c70f..3fc5698 100644 --- a/src/rebar_file_utils.erl +++ b/src/rebar_file_utils.erl @@ -26,7 +26,9 @@ %% ------------------------------------------------------------------- -module(rebar_file_utils). --export([symlink_or_copy/2, +-export([try_consult/1, + format_error/1, + symlink_or_copy/2, rm_rf/1, cp_r/2, mv/2, @@ -34,14 +36,32 @@ write_file_if_contents_differ/2, system_tmpdir/0, system_tmpdir/1, - reset_dir/1]). + reset_dir/1, + touch/1]). -include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). +-include_lib("kernel/include/file.hrl"). + + %% =================================================================== %% Public API %% =================================================================== +try_consult(File) -> + case file:consult(File) of + {ok, Terms} -> + Terms; + {error, enoent} -> + []; + {error, Reason} -> + throw(?PRV_ERROR({bad_term_file, File, Reason})) + end. + +format_error({bad_term_file, AppFile, Reason}) -> + io_lib:format("Error reading file ~s: ~s", [AppFile, file:format_error(Reason)]). + symlink_or_copy(Source, Target) -> Link = case os:type() of {win32, _} -> @@ -55,9 +75,40 @@ symlink_or_copy(Source, Target) -> {error, eexist} -> ok; {error, _} -> - cp_r([Source], Target) + case os:type() of + {win32, _} -> + S = unicode:characters_to_list(Source), + T = unicode:characters_to_list(Target), + case filelib:is_dir(S) of + true -> + win32_symlink(S, T); + false -> + cp_r([S], T) + end; + _ -> + case filelib:is_dir(Target) of + true -> + ok; + false -> + cp_r([Source], Target) + end + end end. +win32_symlink(Source, Target) -> + Res = rebar_utils:sh( + ?FMT("cmd /c mklink /j \"~s\" \"~s\"", + [filename:nativename(Target), filename:nativename(Source)]), + [{use_stdout, false}, return_on_error]), + case win32_ok(Res) of + true -> ok; + false -> + {error, lists:flatten( + io_lib:format("Failed to symlink ~s to ~s~n", + [Source, Target]))} + end. + + %% @doc Remove files and directories. %% Target is a single filename, directoryname or wildcard expression. -spec rm_rf(string()) -> 'ok'. @@ -104,21 +155,24 @@ mv(Source, Dest) -> [{use_stdout, false}, abort_on_error]), ok; {win32, _} -> - {ok, R} = rebar_utils:sh( - ?FMT("move /y \"~s\" \"~s\" 1> nul", + Res = rebar_utils:sh( + ?FMT("robocopy /move /s \"~s\" \"~s\" 1> nul", [filename:nativename(Source), filename:nativename(Dest)]), [{use_stdout, false}, return_on_error]), - case R of - [] -> - ok; - _ -> + case win32_ok(Res) of + true -> ok; + false -> {error, lists:flatten( io_lib:format("Failed to move ~s to ~s~n", [Source, Dest]))} end end. +win32_ok({ok, _}) -> true; +win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true; +win32_ok(_) -> false. + delete_each([]) -> ok; delete_each([File | Rest]) -> @@ -170,6 +224,17 @@ reset_dir(Path) -> %% recreate the directory filelib:ensure_dir(filename:join([Path, "dummy.beam"])). + +%% Linux touch but using erlang functions to work in bot *nix os and +%% windows +-spec touch(Path) -> ok | {error, Reason} when + Path :: file:name(), + Reason :: file:posix(). +touch(Path) -> + {ok, A} = file:read_file_info(Path), + ok = file:write_file_info(Path, A#file_info{mtime = calendar:local_time(), + atime = calendar:local_time()}). + %% =================================================================== %% Internal functions %% =================================================================== @@ -182,28 +247,27 @@ delete_each_dir_win32([Dir | Rest]) -> delete_each_dir_win32(Rest). xcopy_win32(Source,Dest)-> - {ok, R} = rebar_utils:sh( - ?FMT("xcopy \"~s\" \"~s\" /q /y /e 2> nul", + %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to + %% handle long names. May have issues with older windows. + Res = rebar_utils:sh( + ?FMT("robocopy \"~s\" \"~s\" /e /is /purge 2> nul", [filename:nativename(Source), filename:nativename(Dest)]), [{use_stdout, false}, return_on_error]), - case length(R) > 0 of - %% when xcopy fails, stdout is empty and and error message is printed - %% to stderr (which is redirected to nul) - true -> ok; - false -> - {error, lists:flatten( - io_lib:format("Failed to xcopy from ~s to ~s~n", - [Source, Dest]))} + case win32_ok(Res) of + true -> ok; + false -> + {error, lists:flatten( + io_lib:format("Failed to copy ~s to ~s~n", + [Source, Dest]))} end. cp_r_win32({true, SourceDir}, {true, DestDir}) -> %% from directory to directory - SourceBase = filename:basename(SourceDir), - ok = case file:make_dir(filename:join(DestDir, SourceBase)) of + ok = case file:make_dir(DestDir) of {error, eexist} -> ok; Other -> Other end, - ok = xcopy_win32(SourceDir, filename:join(DestDir, SourceBase)); + ok = xcopy_win32(SourceDir, DestDir); cp_r_win32({false, Source} = S,{true, DestDir}) -> %% from file to directory cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))}); diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 2d83579..dfec86a 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -109,7 +109,7 @@ download(Dir, {git, Url, Rev}, _State) -> rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, Dir}]). make_vsn(Dir) -> - {ok, Cwd} = file:get_cwd(), + Cwd = rebar_dir:get_cwd(), try ok = file:set_cwd(Dir), {Vsn, RawRef, RawCount} = collect_default_refcount(), diff --git a/src/rebar_hooks.erl b/src/rebar_hooks.erl index e144a8e..56c4e91 100644 --- a/src/rebar_hooks.erl +++ b/src/rebar_hooks.erl @@ -1,6 +1,10 @@ -module(rebar_hooks). --export([run_all_hooks/5]). +-export([run_all_hooks/5 + ,format_error/1]). + +-include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). -spec run_all_hooks(file:filename_all(), pre | post, atom() | {atom(), atom()} | string(), @@ -10,12 +14,50 @@ run_all_hooks(Dir, Type, Command, Providers, State) -> run_hooks(Dir, Type, Command, State). run_provider_hooks(Dir, Type, Command, Providers, State) -> - State1 = rebar_state:providers(rebar_state:dir(State, Dir), Providers), + PluginDepsPaths = rebar_state:code_paths(State, all_plugin_deps), + code:add_pathsa(PluginDepsPaths), + Providers1 = rebar_state:providers(State), + State1 = rebar_state:providers(rebar_state:dir(State, Dir), Providers++Providers1), AllHooks = rebar_state:get(State1, provider_hooks, []), TypeHooks = proplists:get_value(Type, AllHooks, []), HookProviders = proplists:get_all_values(Command, TypeHooks), - rebar_core:do(HookProviders, State1). + case rebar_core:do(HookProviders, State1) of + {error, ProviderName} -> + ?DEBUG(format_error({bad_provider, Type, Command, ProviderName}), []), + throw(?PRV_ERROR({bad_provider, Type, Command, ProviderName})); + {ok, _} -> + rebar_utils:remove_from_code_path(PluginDepsPaths) + end. + +format_error({bad_provider, Type, Command, {Name, Namespace}}) -> + io_lib:format("Unable to run ~s hooks for '~p', command '~p' in namespace '~p' not found.", [Type, Command, Namespace, Name]); +format_error({bad_provider, Type, Command, Name}) -> + io_lib:format("Unable to run ~s hooks for '~p', command '~p' not found.", [Type, Command, Name]). + +%% @doc The following environment variables are exported when running +%% a hook (absolute paths): +%% +%% REBAR_DEPS_DIR = rebar_dir:deps_dir/1 +%% REBAR_BUILD_DIR = rebar_dir:base_dir/1 +%% REBAR_ROOT_DIR = rebar_dir:root_dir/1 +%% REBAR_CHECKOUTS_DIR = rebar_dir:checkouts_dir/1 +%% REBAR_PLUGINS_DIR = rebar_dir:plugins_dir/1 +%% REBAR_GLOBAL_CONFIG_DIR = rebar_dir:global_config_dir/1 +%% REBAR_GLOBAL_CACHE_DIR = rebar_dir:global_cache_dir/1 +%% REBAR_TEMPLATE_DIR = rebar_dir:template_dir/1 +%% REBAR_APP_DIRS = rebar_dir:lib_dirs/1 +%% REBAR_SRC_DIRS = rebar_dir:src_dirs/1 +%% +%% autoconf compatible variables +%% (see: http://www.gnu.org/software/autoconf/manual/autoconf.html#Erlang-Libraries): +%% ERLANG_ERTS_VER = erlang:system_info(version) +%% ERLANG_ROOT_DIR = code:root_dir/0 +%% ERLANG_LIB_DIR_erl_interface = code:lib_dir(erl_interface) +%% ERLANG_LIB_VER_erl_interface = version part of path returned by code:lib_dir(erl_interface) +%% ERL = ERLANG_ROOT_DIR/bin/erl +%% ERLC = ERLANG_ROOT_DIR/bin/erl +%% run_hooks(Dir, Type, Command, State) -> Hooks = case Type of pre -> @@ -25,7 +67,8 @@ run_hooks(Dir, Type, Command, State) -> _ -> [] end, - Env = [{"REBAR_DEPS_DIR", filename:absname(rebar_dir:deps_dir(State))}], + + Env = create_env(State), lists:foreach(fun({_, C, _}=Hook) when C =:= Command -> apply_hook(Dir, Env, Hook); ({C, _}=Hook) when C =:= Command -> @@ -44,3 +87,33 @@ apply_hook(Dir, Env, {Arch, Command, Hook}) -> apply_hook(Dir, Env, {Command, Hook}) -> Msg = lists:flatten(io_lib:format("Hook for ~p failed!~n", [Command])), rebar_utils:sh(Hook, [use_stdout, {cd, Dir}, {env, Env}, {abort_on_error, Msg}]). + +create_env(State) -> + BaseDir = rebar_state:dir(State), + [ + {"REBAR_DEPS_DIR", filename:absname(rebar_dir:deps_dir(State))}, + {"REBAR_BUILD_DIR", filename:absname(rebar_dir:base_dir(State))}, + {"REBAR_ROOT_DIR", filename:absname(rebar_dir:root_dir(State))}, + {"REBAR_CHECKOUTS_DIR", filename:absname(rebar_dir:checkouts_dir(State))}, + {"REBAR_PLUGINS_DIR", filename:absname(rebar_dir:plugins_dir(State))}, + {"REBAR_GLOBAL_CONFIG_DIR", filename:absname(rebar_dir:global_config_dir(State))}, + {"REBAR_GLOBAL_CACHE_DIR", filename:absname(rebar_dir:global_cache_dir(State))}, + {"REBAR_TEMPLATE_DIR", filename:absname(rebar_dir:template_dir(State))}, + {"REBAR_APP_DIRS", join_dirs(BaseDir, rebar_dir:lib_dirs(State))}, + {"REBAR_SRC_DIRS", join_dirs(BaseDir, rebar_dir:all_src_dirs(State))}, + {"ERLANG_ERTS_VER", erlang:system_info(version)}, + {"ERLANG_ROOT_DIR", code:root_dir()}, + {"ERLANG_LIB_DIR_erl_interface", code:lib_dir(erl_interface)}, + {"ERLANG_LIB_VER_erl_interface", re_version(code:lib_dir(erl_interface))}, + {"ERL", filename:join([code:root_dir(), "bin", "erl"])}, + {"ERLC", filename:join([code:root_dir(), "bin", "erlc"])} + ]. + +join_dirs(BaseDir, Dirs) -> + string:join([ filename:join(BaseDir, Dir) || Dir <- Dirs ], ":"). + +re_version(Path) -> + case re:run(Path, "^.*-(?<VER>[^/-]*)$", [{capture, [1], list}]) of + nomatch -> ""; + {match, [Ver]} -> Ver + end. diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl index e5ad1d2..9f61e71 100644 --- a/src/rebar_otp_app.erl +++ b/src/rebar_otp_app.erl @@ -51,10 +51,10 @@ compile(State, App) -> %% Load the app file and validate it. validate_app(State, App1). -format_error(invalid_app_file) -> - "Failed to read app file"; +format_error({missing_app_file, Filename}) -> + io_lib:format("App file is missing: ~s", [Filename]); format_error({file_read, File, Reason}) -> - io_lib:format("Failed to read ~s for processing: ~p", [File, Reason]); + io_lib:format("Failed to read app file ~s for processing: ~p", [File, file:format_error(Reason)]); format_error({invalid_name, File, AppName}) -> io_lib:format("Invalid ~s: name of application (~p) must match filename.", [File, AppName]). @@ -160,9 +160,8 @@ ebin_modules(State, App, Dir) -> [rebar_utils:beam_to_mod(N) || N <- Filtered]. extra_dirs(State) -> - ErlOpts = rebar_utils:erl_opts(State), - Extras = proplists:get_value(extra_src_dirs, ErlOpts, []), - SrcDirs = proplists:get_value(src_dirs, ErlOpts, ["src"]), + Extras = rebar_dir:extra_src_dirs(State), + SrcDirs = rebar_dir:src_dirs(State, ["src"]), %% remove any dirs that are defined in `src_dirs` from `extra_src_dirs` Extras -- SrcDirs. @@ -198,13 +197,13 @@ ensure_registered(AppData) -> consult_app_file(Filename) -> case filelib:is_file(Filename) of false -> - throw(?PRV_ERROR(invalid_app_file)); + {error, enoent}; true -> case lists:suffix(".app.src", Filename) of false -> file:consult(Filename); true -> - {ok, rebar_config:consult_file(Filename)} + {ok, rebar_config:consult_app_file(Filename)} end end. @@ -215,7 +214,7 @@ app_vsn(AppFile, State) -> Resources = rebar_state:resources(State), rebar_utils:vcs_vsn(get_value(vsn, AppData, AppFile), AppDir, Resources); {error, Reason} -> - ?ABORT("Failed to consult app file ~s: ~p\n", [AppFile, Reason]) + throw(?PRV_ERROR({file_read, AppFile, Reason})) end. get_value(Key, AppInfo, AppFile) -> diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 59ce0dc..5b37788 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -95,7 +95,8 @@ make_vsn(_) -> request(Url, ETag) -> case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]}, [{relaxed, true}], - [{body_format, binary}]) of + [{body_format, binary}], + hex) of {ok, {{_Version, 200, _Reason}, Headers, Body}} -> ?DEBUG("Successfully downloaded ~s", [Url]), {"etag", ETag1} = lists:keyfind("etag", 1, Headers), diff --git a/src/rebar_plugins.erl b/src/rebar_plugins.erl index c16223e..7e12324 100644 --- a/src/rebar_plugins.erl +++ b/src/rebar_plugins.erl @@ -3,7 +3,10 @@ -module(rebar_plugins). --export([install/1, handle_plugins/2]). +-export([project_apps_install/1 + ,install/1 + ,handle_plugins/3 + ,handle_plugins/4]). -include("rebar.hrl"). @@ -11,52 +14,90 @@ %% Public API %% =================================================================== +-spec project_apps_install(rebar_state:t()) -> rebar_state:t(). +project_apps_install(State) -> + Profiles = rebar_state:current_profiles(State), + ProjectApps = rebar_state:project_apps(State), + + lists:foldl(fun(Profile, StateAcc) -> + Plugins = rebar_state:get(State, {plugins, Profile}, []), + StateAcc1 = handle_plugins(Profile, Plugins, StateAcc), + + lists:foldl(fun(App, StateAcc2) -> + AppDir = rebar_app_info:dir(App), + C = rebar_config:consult(AppDir), + S = rebar_state:new(rebar_state:new(), C, AppDir), + Plugins2 = rebar_state:get(S, {plugins, Profile}, []), + handle_plugins(Profile, Plugins2, StateAcc2) + end, StateAcc1, ProjectApps) + end, State, Profiles). + -spec install(rebar_state:t()) -> rebar_state:t(). install(State) -> - Plugins = rebar_state:get(State, plugins, []), + Profiles = rebar_state:current_profiles(State), + lists:foldl(fun(Profile, StateAcc) -> + Plugins = rebar_state:get(State, {plugins, Profile}, []), + handle_plugins(Profile, Plugins, StateAcc) + end, State, Profiles). - ProjectApps = rebar_state:project_apps(State), +handle_plugins(Profile, Plugins, State) -> + handle_plugins(Profile, Plugins, State, false). - OtherPlugins = lists:flatmap(fun(App) -> - AppDir = rebar_app_info:dir(App), - C = rebar_config:consult(AppDir), - S = rebar_state:new(rebar_state:new(), C, AppDir), - rebar_state:get(S, plugins, []) - end, ProjectApps), +handle_plugins(Profile, Plugins, State, Upgrade) -> + %% Set deps dir to plugins dir so apps are installed there + Locks = rebar_state:lock(State), + DepsDir = rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR), + State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR), - handle_plugins(Plugins++OtherPlugins, State). + %% Install each plugin individually so if one fails to install it doesn't effect the others + {_PluginProviders, State2} = + lists:foldl(fun(Plugin, {PluginAcc, StateAcc}) -> + {NewPlugins, NewState} = handle_plugin(Profile, Plugin, StateAcc, Upgrade), + NewState1 = rebar_state:create_logic_providers(NewPlugins, NewState), + {PluginAcc++NewPlugins, NewState1} + end, {[], State1}, Plugins), --spec handle_plugins([rebar_prv_install_deps:dep()], rebar_state:t()) -> rebar_state:t(). -handle_plugins(Plugins, State) -> - PluginProviders = lists:flatmap(fun(Plugin) -> - handle_plugin(Plugin, State) - end, Plugins), - rebar_state:create_logic_providers(PluginProviders, State). + %% reset deps dir + State3 = rebar_state:set(State2, deps_dir, DepsDir), + rebar_state:lock(State3, Locks). -handle_plugin(Plugin, State) -> +handle_plugin(Profile, Plugin, State, Upgrade) -> try - %% Set deps dir to plugins dir so apps are installed there - State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR), - {ok, _, State2} = rebar_prv_install_deps:handle_deps(default, State1, [Plugin]), - - Apps = rebar_state:all_deps(State2), - ToBuild = lists:dropwhile(fun rebar_app_info:valid/1, Apps), - [build_plugin(AppInfo) || AppInfo <- ToBuild], - [true = code:add_patha(filename:join(rebar_app_info:dir(AppInfo), "ebin")) || AppInfo <- Apps], - plugin_providers(Plugin) + {ok, Apps, State2} = rebar_prv_install_deps:handle_deps(Profile, State, [Plugin], Upgrade), + {no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(Apps), + ToBuild = rebar_prv_install_deps:cull_compile(Sorted, []), + + %% Add already built plugin deps to the code path + CodePaths = [rebar_app_info:ebin_dir(A) || A <- Apps -- ToBuild], + code:add_pathsa(CodePaths), + + %% Build plugin and its deps + [build_plugin(AppInfo, Apps, State2) || AppInfo <- ToBuild], + + %% Add newly built deps and plugin to code path + State3 = rebar_state:update_all_plugin_deps(State2, Apps), + NewCodePaths = [rebar_app_info:ebin_dir(A) || A <- ToBuild], + code:add_pathsa(CodePaths), + + %% Store plugin code paths so we can remove them when compiling project apps + State4 = rebar_state:update_code_paths(State3, all_plugin_deps, CodePaths++NewCodePaths), + + {plugin_providers(Plugin), State4} catch C:T -> - ?DEBUG("~p ~p", [C, T]), + ?DEBUG("~p ~p ~p", [C, T, erlang:get_stacktrace()]), ?WARN("Plugin ~p not available. It will not be used.", [Plugin]), - [] + {[], State} end. -build_plugin(AppInfo) -> - AppDir = rebar_app_info:dir(AppInfo), - C = rebar_config:consult(AppDir), - S = rebar_state:new(rebar_state:new(), C, AppDir), - rebar_prv_compile:compile(S, AppInfo). +build_plugin(AppInfo, Apps, State) -> + Providers = rebar_state:providers(State), + Providers1 = rebar_state:providers(rebar_app_info:state(AppInfo)), + S = rebar_state:all_deps(rebar_app_info:state_or_new(State, AppInfo), Apps), + rebar_prv_compile:compile(S, Providers++Providers1, AppInfo). +plugin_providers({Plugin, _, _, _}) when is_atom(Plugin) -> + validate_plugin(Plugin); plugin_providers({Plugin, _, _}) when is_atom(Plugin) -> validate_plugin(Plugin); plugin_providers({Plugin, _}) when is_atom(Plugin) -> diff --git a/src/rebar_prv_app_discovery.erl b/src/rebar_prv_app_discovery.erl index 97862c1..ea55e11 100644 --- a/src/rebar_prv_app_discovery.erl +++ b/src/rebar_prv_app_discovery.erl @@ -23,7 +23,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, true}, + {bare, false}, {deps, ?DEPS}, {example, ""}, {short_desc, ""}, @@ -38,7 +38,7 @@ do(State) -> State1 = rebar_app_discover:do(State, LibDirs), {ok, State1} catch - throw:{error, Error}-> + throw:{error, Error} -> ?PRV_ERROR(Error) end. @@ -46,12 +46,17 @@ do(State) -> format_error({multiple_app_files, Files}) -> io_lib:format("Multiple app files found in one app dir: ~s", [string:join(Files, " and ")]); format_error({invalid_app_file, File, Reason}) -> - case Reason of + case Reason of {Line, erl_parse, Description} -> - io_lib:format("Invalid app file ~s at line ~b: ~p", + io_lib:format("Invalid app file ~s at line ~b: ~p", [File, Line, lists:flatten(Description)]); _ -> io_lib:format("Invalid app file ~s: ~p", [File, Reason]) end; +%% Provide a slightly more informative error message for consult of app file failure +format_error({rebar_file_utils, {bad_term_file, AppFile, Reason}}) -> + io_lib:format("Error in app file ~s: ~s", [rebar_dir:make_relative_path(AppFile, + rebar_dir:get_cwd()), + file:format_error(Reason)]); format_error(Reason) -> io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_as.erl b/src/rebar_prv_as.erl index 64ad951..ead7b01 100644 --- a/src/rebar_prv_as.erl +++ b/src/rebar_prv_as.erl @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 as <profile1>,<profile2>,... <task1>, <task2>, ..."}, {short_desc, "Higher order provider for running multiple tasks in a sequence as a certain profiles."}, @@ -38,13 +38,14 @@ do(State) -> {error, "At least one profile must be specified when using `as`"}; _ -> State1 = rebar_state:apply_profiles(State, [list_to_atom(X) || X <- Profiles]), + State2 = rebar_plugins:project_apps_install(State1), {FirstTask, FirstTaskArgs} = hd(Tasks), FirstTaskAtom = list_to_atom(FirstTask), - case rebar_core:process_namespace(State1, FirstTaskAtom) of - {ok, State2, NewTask} -> + case rebar_core:process_namespace(State2, FirstTaskAtom) of + {ok, State3, NewTask} -> rebar_prv_do:do_tasks( [{atom_to_list(NewTask),FirstTaskArgs}|tl(Tasks)], - State2 + State3 ); {error, Reason} -> {error, Reason} diff --git a/src/rebar_prv_clean.erl b/src/rebar_prv_clean.erl index 8fafe23..e3cb84e 100644 --- a/src/rebar_prv_clean.erl +++ b/src/rebar_prv_clean.erl @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 clean"}, {short_desc, "Remove compiled beam files from apps."}, @@ -67,11 +67,9 @@ format_error(Reason) -> clean_apps(State, Providers, Apps) -> lists:foreach(fun(AppInfo) -> AppDir = rebar_app_info:dir(AppInfo), - C = rebar_config:consult(AppDir), - S = rebar_state:new(State, C, AppDir), + S = rebar_app_info:state_or_new(State, AppInfo), ?INFO("Cleaning out ~s...", [rebar_app_info:name(AppInfo)]), - %% Legacy hook support rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, S), rebar_erlc_compiler:clean(State, rebar_app_info:out_dir(AppInfo)), rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, S) diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index eb51d8d..2b024cf 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -25,7 +25,7 @@ init(State) -> Provider = providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {deps, ?DEPS}, - {bare, false}, + {bare, true}, {example, "rebar3 ct"}, {short_desc, "Run Common Tests."}, {desc, "Run Common Tests."}, @@ -38,7 +38,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Running Common Test suites...", []), - code:add_pathsa(rebar_state:code_paths(State, all_deps)), + rebar_utils:update_code(rebar_state:code_paths(State, all_deps)), %% Run ct provider prehooks Providers = rebar_state:providers(State), @@ -49,7 +49,7 @@ do(State) -> {ok, State1} = Result -> %% Run ct provider posthooks rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), - rebar_utils:cleanup_code_path(rebar_state:code_paths(State1, default)), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), Result; ?PRV_ERROR(_) = Error -> rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), @@ -105,14 +105,18 @@ run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)). run_test_quiet(Opts) -> Pid = self(), + Ref = erlang:make_ref(), LogDir = proplists:get_value(logdir, Opts), - erlang:spawn_monitor(fun() -> + {_, Monitor} = erlang:spawn_monitor(fun() -> {ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]), [write]), true = group_leader(F, self()), - Pid ! ct:run_test(Opts) + Pid ! {Ref, ct:run_test(Opts)} end), - receive Result -> handle_quiet_results(Opts, Result) end. + receive + {Ref, Result} -> handle_quiet_results(Opts, Result); + {'DOWN', Monitor, _, _, Reason} -> handle_results(?PRV_ERROR(Reason)) + end. handle_results(Results) when is_list(Results) -> Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results), @@ -132,8 +136,6 @@ sum_results({Passed, Failed, {UserSkipped, AutoSkipped}}, handle_quiet_results(_, {error, _} = Result) -> handle_results(Result); -handle_quiet_results(_, {'DOWN', _, _, _, Reason}) -> - handle_results(?PRV_ERROR(Reason)); handle_quiet_results(CTOpts, Results) when is_list(Results) -> _ = [format_result(Result) || Result <- Results], case handle_results(Results) of @@ -247,7 +249,7 @@ copy_and_compile_test_suites(State, Opts) -> Dirs = find_suite_dirs(AllSuites), lists:foreach(fun(S) -> NewPath = copy(State, S), - compile_dir(State, S, NewPath) + compile_dir(State, NewPath) end, Dirs), NewSuites = lists:map(fun(S) -> retarget_path(State, S) end, AllSuites), [{suite, NewSuites}|lists:keydelete(suite, 1, Opts)] @@ -259,12 +261,12 @@ copy_and_compile_test_dirs(State, Opts) -> %% dir is a single directory Dir when is_list(Dir), is_integer(hd(Dir)) -> NewPath = copy(State, Dir), - [{dir, compile_dir(State, Dir, NewPath)}|lists:keydelete(dir, 1, Opts)]; + [{dir, compile_dir(State, NewPath)}|lists:keydelete(dir, 1, Opts)]; %% dir is a list of directories Dirs when is_list(Dirs) -> NewDirs = lists:map(fun(Dir) -> NewPath = copy(State, Dir), - compile_dir(State, Dir, NewPath) + compile_dir(State, NewPath) end, Dirs), [{dir, NewDirs}|lists:keydelete(dir, 1, Opts)] end. @@ -301,11 +303,11 @@ copy(State, Dir) -> Target end. -compile_dir(State, Dir, OutDir) -> - NewState = replace_src_dirs(State, [Dir]), - ok = rebar_erlc_compiler:compile(NewState, rebar_state:dir(State), OutDir), +compile_dir(State, Dir) -> + NewState = replace_src_dirs(State, [filename:absname(Dir)]), + ok = rebar_erlc_compiler:compile(NewState, rebar_dir:base_dir(State), Dir), ok = maybe_cover_compile(State, Dir), - OutDir. + Dir. retarget_path(State, Path) -> ProjectApps = rebar_state:project_apps(State), @@ -345,31 +347,39 @@ reduce_path([_|Acc], [".."|Rest]) -> reduce_path(Acc, Rest); reduce_path([], [".."|Rest]) -> reduce_path([], Rest); reduce_path(Acc, [Component|Rest]) -> reduce_path([Component|Acc], Rest). -remove_links(Path) -> - case ec_file:is_dir(Path) of - false -> ok; - true -> remove_links1(Path) - end. -remove_links1(Path) -> +remove_links(Path) -> + IsDir = ec_file:is_dir(Path), case ec_file:is_symlink(Path) of - true -> - file:delete(Path); - false -> - lists:foreach(fun(ChildPath) -> - remove_links(ChildPath) - end, sub_dirs(Path)) + true when IsDir -> + delete_dir_link(Path); + false when IsDir -> + lists:foreach(fun(ChildPath) -> + remove_links(ChildPath) + end, dir_entries(Path)); + _ -> file:delete(Path) end. -sub_dirs(Path) -> +delete_dir_link(Path) -> + case os:type() of + {unix, _} -> file:delete(Path); + {win32, _} -> file:del_dir(Path) + end. + +dir_entries(Path) -> {ok, SubDirs} = file:list_dir(Path), [filename:join(Path, SubDir) || SubDir <- SubDirs]. replace_src_dirs(State, Dirs) -> %% replace any `src_dirs` with the test dirs ErlOpts = rebar_state:get(State, erl_opts, []), - StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts), - rebar_state:set(State, erl_opts, [{src_dirs, Dirs}|StrippedOpts]). + StrippedErlOpts = filter_src_dirs(ErlOpts), + State1 = rebar_state:set(State, erl_opts, StrippedErlOpts), + State2 = rebar_state:set(State1, src_dirs, []), + rebar_state:set(State2, extra_src_dirs, Dirs). + +filter_src_dirs(ErlOpts) -> + lists:filter(fun({src_dirs, _}) -> false; ({extra_src_dirs, _}) -> false; (_) -> true end, ErlOpts). test_dirs(State, Opts) -> BareTest = filename:join([rebar_state:dir(State), "test"]), diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl index f70ca28..6eb8a4f 100644 --- a/src/rebar_prv_compile.erl +++ b/src/rebar_prv_compile.erl @@ -6,8 +6,9 @@ do/1, format_error/1]). --export([compile/2]). +-export([compile/3]). +-include_lib("providers/include/providers.hrl"). -include("rebar.hrl"). -define(PROVIDER, compile). @@ -21,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 compile"}, {short_desc, "Compile apps .app.src and .erl files."}, @@ -32,17 +33,21 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> DepsPaths = rebar_state:code_paths(State, all_deps), + PluginDepsPaths = rebar_state:code_paths(State, all_plugin_deps), + rebar_utils:remove_from_code_path(PluginDepsPaths), code:add_pathsa(DepsPaths), ProjectApps = rebar_state:project_apps(State), Providers = rebar_state:providers(State), Deps = rebar_state:deps_to_build(State), - Cwd = rebar_dir:get_cwd(), + Cwd = rebar_state:dir(State), - %% Need to allow global config vars used on deps - %% Right now no way to differeniate and just give deps a new state + %% Need to allow global config vars used on deps. + %% Right now no way to differeniate and just give deps a new state. + %% But need an account of "all deps" for some hooks to use. EmptyState = rebar_state:new(), - build_apps(EmptyState, Providers, Deps), + build_apps(rebar_state:all_deps(EmptyState, + rebar_state:all_deps(State)), Providers, Deps), {ok, ProjectApps1} = rebar_digraph:compile_order(ProjectApps), @@ -56,12 +61,15 @@ do(State) -> State3 = rebar_state:code_paths(State2, all_deps, DepsPaths ++ ProjAppsPaths), rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State2), + has_all_artifacts(State3), rebar_utils:cleanup_code_path(rebar_state:code_paths(State3, default)), {ok, State3}. -spec format_error(any()) -> iolist(). +format_error({missing_artifact, File}) -> + io_lib:format("Missing artifact ~s", [File]); format_error(Reason) -> io_lib:format("~p", [Reason]). @@ -71,29 +79,22 @@ build_apps(State, Providers, Apps) -> build_app(State, Providers, AppInfo) -> AppDir = rebar_app_info:dir(AppInfo), OutDir = rebar_app_info:out_dir(AppInfo), - copy_app_dirs(State, AppDir, OutDir), - S = case rebar_app_info:state(AppInfo) of - undefined -> - C = rebar_config:consult(AppDir), - rebar_state:new(State, C, AppDir); - AppState -> - AppState - end, - - %% Legacy hook support - rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, S), - AppInfo1 = compile(S, AppInfo), - rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, S), + S = rebar_app_info:state_or_new(State, AppInfo), + S1 = rebar_state:all_deps(S, rebar_state:all_deps(State)), + compile(S1, Providers, AppInfo). - AppInfo1. - -compile(State, AppInfo) -> +compile(State, Providers, AppInfo) -> ?INFO("Compiling ~s", [rebar_app_info:name(AppInfo)]), + AppDir = rebar_app_info:dir(AppInfo), + rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, State), + rebar_erlc_compiler:compile(State, ec_cnv:to_list(rebar_app_info:out_dir(AppInfo))), case rebar_otp_app:compile(State, AppInfo) of {ok, AppInfo1} -> + rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, State), + has_all_artifacts(State), AppInfo1; Error -> throw(Error) @@ -103,6 +104,14 @@ compile(State, AppInfo) -> %% Internal functions %% =================================================================== +has_all_artifacts(State) -> + case rebar_state:has_all_artifacts(State) of + {false, File} -> + throw(?PRV_ERROR({missing_artifact, File})); + true -> + true + end. + copy_app_dirs(State, OldAppDir, AppDir) -> case ec_cnv:to_binary(filename:absname(OldAppDir)) =/= ec_cnv:to_binary(filename:absname(AppDir)) of @@ -119,9 +128,7 @@ copy_app_dirs(State, OldAppDir, AppDir) -> end, filelib:ensure_dir(filename:join(AppDir, "dummy")), %% link to src_dirs to be adjacent to ebin is needed for R15 use of cover/xref - ErlOpts = rebar_utils:erl_opts(State), - SrcDirs = proplists:get_value(src_dirs, ErlOpts, ["src"]) ++ - proplists:get_value(extra_src_dirs, ErlOpts, []), + SrcDirs = rebar_dir:all_src_dirs(State, ["src"], ["test"]), [symlink_or_copy(OldAppDir, AppDir, Dir) || Dir <- ["priv", "include"] ++ SrcDirs]; false -> ok diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index 900c569..8c26521 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -25,7 +25,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 cover"}, {short_desc, "Perform coverage analysis."}, diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 96e2277..1cf7b71 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -14,6 +14,7 @@ -define(PROVIDER, dialyzer). -define(DEPS, [compile]). +-define(PLT_PREFIX, "rebar3"). %% =================================================================== %% Public API @@ -25,7 +26,7 @@ init(State) -> {succ_typings, $s, "succ-typings", boolean, "Enable success typing analysis. Default: true"}], State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 dialyzer"}, {short_desc, short_desc()}, @@ -39,33 +40,42 @@ desc() -> "This command will build, and keep up-to-date, a suitable PLT and will use " "it to carry out success typing analysis on the current project.\n" "\n" - "The following (optional) configurations can be added to a rebar.config:\n" - "`dialyzer_warnings` - a list of dialyzer warnings\n" - "`dialyzer_plt` - the PLT file to use\n" - "`dialyzer_plt_apps` - a list of applications to include in the PLT file*\n" - "`dialyzer_plt_warnings` - display warnings when updating a PLT file " - "(boolean)\n" - "`dialyzer_base_plt` - the base PLT file to use**\n" - "`dialyzer_base_plt_dir` - the base PLT directory**\n" - "`dialyzer_base_plt_apps` - a list of applications to include in the base " - "PLT file**\n" + "The following (optional) configurations can be added to a `proplist` of " + "options `dialyzer` in rebar.config:\n" + "`warnings` - a list of dialyzer warnings\n" + "`get_warnings` - display warnings when altering a PLT file (boolean)\n" + "`plt_extra_apps` - a list of applications to include in the PLT file*\n" + "`plt_location` - the location of the PLT file, `local` to store in the " + "profile's base directory (default) or a custom directory.\n" + "`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"**\n" + "`base_plt_apps` - a list of applications to include in the base " + "PLT file***\n" + "`base_plt_location` - the location of base PLT file, `global` to store in " + "$HOME/.cache/rebar3 (default) or a custom directory***\n" + "`base_plt_prefix` - the prefix to the base PLT file, defaults to " + "\"rebar3\"** ***\n" + "\n" + "For example, to warn on unmatched returns: \n" + "{dialyzer, [{warnings, [unmatched_returns]}]}.\n" "\n" "*The applications in `dialyzer_base_plt_apps` and any `applications` and " "`included_applications` listed in their .app files will be added to the " "list.\n" - "**The base PLT is a PLT containing the core OTP applications often " - "required for a project's PLT. One base PLT is created per OTP version and " - "stored in `dialyzer_base_plt_dir` (defaults to $HOME/.rebar3/). A base " - "PLT is used to create a project's initial PLT.". + "**PLT files are named \"<prefix>_<otp_release>_plt\".\n" + "***The base PLT is a PLT containing the core applications often required " + "for a project's PLT. One base PLT is created per OTP version and " + "stored in `base_plt_location`. A base PLT is used to build project PLTs." + "\n". short_desc() -> "Run the Dialyzer analyzer on the project.". -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> + maybe_fix_env(), ?INFO("Dialyzer starting, this may take a while...", []), code:add_pathsa(rebar_state:code_paths(State, all_deps)), - Plt = get_plt_location(State), + Plt = get_plt(State), try do(State, Plt) @@ -74,17 +84,28 @@ do(State) -> ?PRV_ERROR({error_processing_apps, Error}); throw:{dialyzer_warnings, Warnings} -> ?PRV_ERROR({dialyzer_warnings, Warnings}); + throw:{unknown_application, _} = Error -> + ?PRV_ERROR(Error); throw:{output_file_error, _, _} = Error -> ?PRV_ERROR(Error) after rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)) end. +%% This is used to workaround dialyzer quirk discussed here +%% https://github.com/rebar/rebar3/pull/489#issuecomment-107953541 +%% Dialyzer gets default plt location wrong way by peeking HOME environment +%% variable which usually is not defined on Windows. +maybe_fix_env() -> + os:putenv("DIALYZER_PLT", filename:join(rebar_dir:home_dir(), ".dialyzer_plt")). + -spec format_error(any()) -> iolist(). format_error({error_processing_apps, Error}) -> io_lib:format("Error in dialyzing apps: ~s", [Error]); format_error({dialyzer_warnings, Warnings}) -> io_lib:format("Warnings occured running dialyzer: ~b", [Warnings]); +format_error({unknown_application, App}) -> + io_lib:format("Could not find application: ~s", [App]); format_error({output_file_error, File, Error}) -> Error1 = file:format_error(Error), io_lib:format("Failed to write to ~s: ~s", [File, Error1]); @@ -93,13 +114,19 @@ format_error(Reason) -> %% Internal functions -get_plt_location(State) -> - BaseDir = rebar_dir:base_dir(State), - DefaultPlt = filename:join(BaseDir, default_plt()), - rebar_state:get(State, dialyzer_plt, DefaultPlt). +get_plt(State) -> + Prefix = get_config(State, plt_prefix, ?PLT_PREFIX), + Name = plt_name(Prefix), + case get_config(State, plt_location, local) of + local -> + BaseDir = rebar_dir:base_dir(State), + filename:join(BaseDir, Name); + Dir -> + filename:join(Dir, Name) + end. -default_plt() -> - rebar_utils:otp_release() ++ ".plt". +plt_name(Prefix) -> + Prefix ++ "_" ++ rebar_utils:otp_release() ++ "_plt". do(State, Plt) -> Output = get_output_file(State), @@ -138,21 +165,17 @@ update_proj_plt(State, Plt, Output) -> do_update_proj_plt(State, Plt, Output) -> ?INFO("Updating plt...", []), - {Files, Warnings} = proj_plt_files(State), - Warnings2 = format_warnings(Output, Warnings), - {Warnings3, State2} = case read_plt(State, Plt) of - {ok, OldFiles} -> - check_plt(State, Plt, Output, OldFiles, - Files); - {error, no_such_file} -> - build_proj_plt(State, Plt, Output, Files) - end, - {Warnings2 + Warnings3, State2}. + Files = proj_plt_files(State), + case read_plt(State, Plt) of + {ok, OldFiles} -> + check_plt(State, Plt, Output, OldFiles, Files); + {error, no_such_file} -> + build_proj_plt(State, Plt, Output, Files) + end. proj_plt_files(State) -> - BasePltApps = rebar_state:get(State, dialyzer_base_plt_apps, - default_plt_apps()), - PltApps = rebar_state:get(State, dialyzer_plt_apps, []), + BasePltApps = get_config(State, base_plt_apps, default_plt_apps()), + PltApps = get_config(State, plt_extra_apps, []), Apps = rebar_state:project_apps(State), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), get_plt_files(BasePltApps ++ PltApps ++ DepApps, Apps). @@ -165,23 +188,18 @@ default_plt_apps() -> get_plt_files(DepApps, Apps) -> ?INFO("Resolving files...", []), - get_plt_files(DepApps, Apps, [], [], []). + get_plt_files(DepApps, Apps, [], []). -get_plt_files([], _, _, Files, Warnings) -> - {Files, Warnings}; -get_plt_files([AppName | DepApps], Apps, PltApps, Files, Warnings) -> +get_plt_files([], _, _, Files) -> + Files; +get_plt_files([AppName | DepApps], Apps, PltApps, Files) -> case lists:member(AppName, PltApps) orelse app_member(AppName, Apps) of true -> - get_plt_files(DepApps, Apps, PltApps, Files, Warnings); + get_plt_files(DepApps, Apps, PltApps, Files); false -> - {DepApps2, Files2, Warnings2} = app_name_to_info(AppName), - ?DEBUG("~s dependencies: ~p", [AppName, DepApps2]), + Files2 = app_files(AppName), ?DEBUG("~s files: ~p", [AppName, Files2]), - DepApps3 = DepApps2 ++ DepApps, - PltApps2 = [AppName | PltApps], - Files3 = Files2 ++ Files, - Warnings3 = Warnings2 ++ Warnings, - get_plt_files(DepApps3, Apps, PltApps2, Files3, Warnings3) + get_plt_files(DepApps, Apps, [AppName | PltApps], Files2 ++ Files) end. app_member(AppName, Apps) -> @@ -192,71 +210,28 @@ app_member(AppName, Apps) -> false end. -app_name_to_info(AppName) -> - case app_name_to_ebin(AppName) of - {error, _} -> - {[], [], [{unknown_application, {"", 0}, [AppName]}]}; - EbinDir -> - ebin_to_info(EbinDir, AppName) +app_files(AppName) -> + case app_ebin(AppName) of + {ok, EbinDir} -> + ebin_files(EbinDir); + {error, bad_name} -> + throw({unknown_application, AppName}) end. -app_name_to_ebin(AppName) -> +app_ebin(AppName) -> case code:lib_dir(AppName, ebin) of - {error, bad_name} -> - search_ebin(AppName); + {error, bad_name} = Error -> + Error; EbinDir -> - check_ebin(EbinDir, AppName) + check_ebin(EbinDir) end. -check_ebin(EbinDir, AppName) -> +check_ebin(EbinDir) -> case filelib:is_dir(EbinDir) of true -> - EbinDir; - false -> - search_ebin(AppName) - end. - -search_ebin(AppName) -> - case code:where_is_file(atom_to_list(AppName) ++ ".app") of - non_existing -> - {error, bad_name}; - AppFile -> - filename:dirname(AppFile) - end. - -ebin_to_info(EbinDir, AppName) -> - AppFile = filename:join(EbinDir, atom_to_list(AppName) ++ ".app"), - ?DEBUG("Consulting app file ~p", [AppFile]), - case file:consult(AppFile) of - {ok, [{application, AppName, AppDetails}]} -> - DepApps = proplists:get_value(applications, AppDetails, []), - IncApps = proplists:get_value(included_applications, AppDetails, - []), - Modules = proplists:get_value(modules, AppDetails, []), - {Files, Warnings} = modules_to_files(Modules, EbinDir), - {IncApps ++ DepApps, Files, Warnings}; - {error, enoent} when AppName =:= erts -> - {[], ebin_files(EbinDir), []}; - _ -> - Error = io_lib:format("Could not parse ~p", [AppFile]), - throw({dialyzer_error, Error}) - end. - -modules_to_files(Modules, EbinDir) -> - Ext = code:objfile_extension(), - Result = [module_to_file(Module, EbinDir, Ext) || Module <- Modules], - Files = [File || {_, File} <- Result, File =/= unknown], - Warnings = [{unknown_module, {"", 0}, [Module]} || - {Module, unknown} <- Result], - {Files, Warnings}. - -module_to_file(Module, EbinDir, Ext) -> - File = filename:join(EbinDir, atom_to_list(Module) ++ Ext), - case filelib:is_file(File) of - true -> - {Module, File}; + {ok, EbinDir}; false -> - {Module, unknown} + {error, bad_name} end. ebin_files(EbinDir) -> @@ -306,43 +281,46 @@ add_plt(State, Plt, Output, Files) -> run_plt(State, Plt, Output, plt_add, Files). run_plt(State, Plt, Output, Analysis, Files) -> - GetWarnings = rebar_state:get(State, dialyzer_plt_warnings, false), + GetWarnings = get_config(State, get_warnings, false), Opts = [{analysis_type, Analysis}, {get_warnings, GetWarnings}, {init_plt, Plt}, + {output_plt, Plt}, {from, byte_code}, {files, Files}], run_dialyzer(State, Opts, Output). build_proj_plt(State, Plt, Output, Files) -> - BasePlt = get_base_plt_location(State), + BasePlt = get_base_plt(State), ?INFO("Updating base plt...", []), - {BaseFiles, BaseWarnings} = base_plt_files(State), - BaseWarnings2 = format_warnings(Output, BaseWarnings), - {BaseWarnings3, State1} = update_base_plt(State, BasePlt, Output, - BaseFiles), + BaseFiles = base_plt_files(State), + {BaseWarnings, State1} = update_base_plt(State, BasePlt, Output, BaseFiles), ?INFO("Copying ~p to ~p...", [BasePlt, Plt]), _ = filelib:ensure_dir(Plt), case file:copy(BasePlt, Plt) of {ok, _} -> {CheckWarnings, State2} = check_plt(State1, Plt, Output, BaseFiles, Files), - {BaseWarnings2 + BaseWarnings3 + CheckWarnings, State2}; + {BaseWarnings + CheckWarnings, State2}; {error, Reason} -> Error = io_lib:format("Could not copy PLT from ~p to ~p: ~p", [BasePlt, Plt, file:format_error(Reason)]), throw({dialyzer_error, Error}) end. -get_base_plt_location(State) -> - GlobalCacheDir = rebar_dir:global_cache_dir(State), - BaseDir = rebar_state:get(State, dialyzer_base_plt_dir, GlobalCacheDir), - BasePlt = rebar_state:get(State, dialyzer_base_plt, default_plt()), - filename:join(BaseDir, BasePlt). +get_base_plt(State) -> + Prefix = get_config(State, base_plt_prefix, ?PLT_PREFIX), + Name = plt_name(Prefix), + case get_config(State, base_plt_location, global) of + global -> + GlobalCacheDir = rebar_dir:global_cache_dir(State), + filename:join(GlobalCacheDir, Name); + Dir -> + filename:join(Dir, Name) + end. base_plt_files(State) -> - BasePltApps = rebar_state:get(State, dialyzer_base_plt_apps, - default_plt_apps()), + BasePltApps = get_config(State, base_plt_apps, default_plt_apps()), Apps = rebar_state:project_apps(State), get_plt_files(BasePltApps, Apps). @@ -357,7 +335,7 @@ update_base_plt(State, BasePlt, Output, BaseFiles) -> build_plt(State, Plt, Output, Files) -> ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), - GetWarnings = rebar_state:get(State, dialyzer_plt_warnings, false), + GetWarnings = get_config(State, get_warnings, false), Opts = [{analysis_type, plt_build}, {get_warnings, GetWarnings}, {output_plt, Plt}, @@ -376,36 +354,29 @@ succ_typings(State, Plt, Output) -> succ_typings(State, Plt, Output, Apps) -> ?INFO("Doing success typing analysis...", []), - {Files, Warnings} = apps_to_files(Apps), - Warnings2 = format_warnings(Output, Warnings), + Files = apps_to_files(Apps), ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]), Opts = [{analysis_type, succ_typings}, {get_warnings, true}, {from, byte_code}, {files, Files}, {init_plt, Plt}], - {Warnings3, State2} = run_dialyzer(State, Opts, Output), - {Warnings2 + Warnings3, State2}. + run_dialyzer(State, Opts, Output). apps_to_files(Apps) -> ?INFO("Resolving files...", []), - Result = [{Files, Warnings} || - App <- Apps, - {Files, Warnings} <- [app_to_files(App)]], - Files = [File || {Files, _} <- Result, File <- Files], - Warnings = [Warning || {_, Warnings} <- Result, Warning <- Warnings], - {Files, Warnings}. + [File || App <- Apps, + File <- app_to_files(App)]. app_to_files(App) -> AppName = ec_cnv:to_atom(rebar_app_info:name(App)), - {_, Files, Warnings} = app_name_to_info(AppName), - {Files, Warnings}. + app_files(AppName). run_dialyzer(State, Opts, Output) -> %% dialyzer may return callgraph warnings when get_warnings is false case proplists:get_bool(get_warnings, Opts) of true -> - WarningsList = rebar_state:get(State, dialyzer_warnings, []), + WarningsList = get_config(State, warnings, []), Opts2 = [{warnings, WarningsList}, {check_plt, false} | Opts], @@ -417,7 +388,7 @@ run_dialyzer(State, Opts, Output) -> {check_plt, false} | Opts], ?DEBUG("Running dialyzer with options: ~p~n", [Opts2]), - _ = dialyzer:run(Opts2), + dialyzer:run(Opts2), {0, State} end. @@ -430,10 +401,6 @@ format_warnings(Output, Warnings) -> format_warnings(Warnings) -> [format_warning(Warning) || Warning <- Warnings]. -format_warning({unknown_application, _, [AppName]}) -> - io_lib:format("Unknown application: ~s", [AppName]); -format_warning({unknown_module, _, [Module]}) -> - io_lib:format("Unknown module: ~s", [Module]); format_warning(Warning) -> case strip(dialyzer:format_warning(Warning, fullpath)) of ":0: " ++ Unknown -> @@ -471,3 +438,7 @@ no_warnings() -> no_contracts, no_behaviours, no_undefined_callbacks]. + +get_config(State, Key, Default) -> + Config = rebar_state:get(State, dialyzer, []), + proplists:get_value(Key, Config, Default). diff --git a/src/rebar_prv_do.erl b/src/rebar_prv_do.erl index aee3a27..f850135 100644 --- a/src/rebar_prv_do.erl +++ b/src/rebar_prv_do.erl @@ -23,7 +23,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 do <task1>, <task2>, ..."}, {short_desc, "Higher order provider for running multiple tasks in a sequence."}, @@ -33,8 +33,16 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Tasks = rebar_utils:args_to_tasks(rebar_state:command_args(State)), - do_tasks(Tasks, State). + case rebar_utils:args_to_tasks(rebar_state:command_args(State)) of + [] -> + AllProviders = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + Providers = providers:get_providers_by_namespace(Namespace, AllProviders), + providers:help(Providers), + {ok, State}; + Tasks -> + do_tasks(Tasks, State) + end. do_tasks([], State) -> {ok, State}; @@ -47,6 +55,8 @@ do_tasks([{TaskStr, Args}|Tail], State) -> default -> %% The first task we hit might be a namespace! case maybe_namespace(State2, Task, Args) of + {ok, FinalState} when Tail =:= [] -> + {ok, FinalState}; {ok, _} -> do_tasks(Tail, State); {error, Reason} -> @@ -56,6 +66,8 @@ do_tasks([{TaskStr, Args}|Tail], State) -> %% We're already in a non-default namespace, check the %% task directly. case rebar_core:process_command(State2, Task) of + {ok, FinalState} when Tail =:= [] -> + {ok, FinalState}; {ok, _} -> do_tasks(Tail, State); {error, Reason} -> diff --git a/src/rebar_prv_edoc.erl b/src/rebar_prv_edoc.erl index efcfeb5..14df269 100644 --- a/src/rebar_prv_edoc.erl +++ b/src/rebar_prv_edoc.erl @@ -19,7 +19,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 edoc"}, {short_desc, "Generate documentation using edoc."}, diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl index 03332a0..3cdc9bf 100644 --- a/src/rebar_prv_escriptize.erl +++ b/src/rebar_prv_escriptize.erl @@ -47,7 +47,7 @@ init(State) -> Provider = providers:create([ {name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 escriptize"}, {opts, []}, @@ -72,7 +72,7 @@ do(State) -> end; Name -> AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), - AppInfo = rebar_app_utils:find(Name, AllApps), + {ok, AppInfo} = rebar_app_utils:find(ec_cnv:to_binary(Name), AllApps), escriptize(State, AppInfo) end. @@ -83,14 +83,15 @@ escriptize(State0, App) -> %% Get the output filename for the escript -- this may include dirs Filename = filename:join([rebar_dir:base_dir(State0), "bin", rebar_state:get(State0, escript_name, AppName)]), + ?DEBUG("Creating escript file ~s", [Filename]), ok = filelib:ensure_dir(Filename), State = rebar_state:escript_path(State0, Filename), %% Look for a list of other applications (dependencies) to include %% in the output file. We then use the .app files for each of these %% to pull in all the .beam files. - InclApps = lists:usort(rebar_state:get(State, escript_incl_apps, []) - ++ all_deps(State)), + InclApps = lists:usort([ec_cnv:to_atom(AppName) | rebar_state:get(State, escript_incl_apps, []) + ++ all_deps(State)]), AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), InclBeams = get_apps_beams(InclApps, AllApps), @@ -134,7 +135,7 @@ format_error({bad_name, App}) -> io_lib:format("Failed to get ebin/ directory for " "escript_incl_app: ~p", [App]); format_error(no_main_app) -> - io_lib:format("Multiple project apps and {rebar_escript_plugin, [{main_app, atom()}]}." + io_lib:format("Multiple project apps and {escript_main_app, atom()}." " not set in rebar.config", []). %% =================================================================== diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 8eaa926..28c0ed6 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -24,7 +24,7 @@ init(State) -> Provider = providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {deps, ?DEPS}, - {bare, false}, + {bare, true}, {example, "rebar3 eunit"}, {short_desc, "Run EUnit Tests."}, {desc, "Run EUnit Tests."}, @@ -37,7 +37,8 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Performing EUnit tests...", []), - code:add_pathsa(rebar_state:code_paths(State, all_deps)), + rebar_utils:update_code(rebar_state:code_paths(State, all_deps)), + %% Run eunit provider prehooks Providers = rebar_state:providers(State), Cwd = rebar_dir:get_cwd(), @@ -49,7 +50,7 @@ do(State) -> {ok, State1} -> %% Run eunit provider posthooks rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), - rebar_utils:cleanup_code_path(rebar_state:code_paths(State1, default)), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), {ok, State1}; Error -> rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), @@ -84,8 +85,7 @@ format_error({error_running_tests, Reason}) -> test_state(State) -> ErlOpts = rebar_state:get(State, eunit_compile_opts, []), TestOpts = safe_define_test_macro(ErlOpts), - TestDir = [{extra_src_dirs, ["test"]}], - first_files(State) ++ [{erl_opts, TestOpts ++ TestDir}]. + first_files(State) ++ [{erl_opts, TestOpts}]. safe_define_test_macro(Opts) -> %% defining a compile macro twice results in an exception so @@ -106,38 +106,49 @@ first_files(State) -> prepare_tests(State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), - ok = maybe_cover_compile(State, RawOpts), - ProjectApps = project_apps(State), - resolve_apps(ProjectApps, RawOpts). - -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). + resolve_apps(State, RawOpts). -resolve_apps(ProjectApps, RawOpts) -> +resolve_apps(State, RawOpts) -> case proplists:get_value(app, RawOpts) of - undefined -> resolve_suites(ProjectApps, RawOpts); + undefined -> resolve_suites(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(TestApps, RawOpts); + {ok, TestApps} -> resolve_suites(State, TestApps, RawOpts); Error -> Error end end. -resolve_suites(Apps, RawOpts) -> +resolve_suites(State, RawOpts) -> resolve_suites(State, project_apps(State), RawOpts). + +resolve_suites(State, Apps, RawOpts) -> case proplists:get_value(suite, RawOpts) of - undefined -> test_set(Apps, all); + undefined -> compile_tests(State, Apps, all, RawOpts); Suites -> SuiteNames = string:tokens(Suites, [$,]), case filter_suites_by_apps(SuiteNames, Apps) of - {ok, S} -> test_set(Apps, S); + {ok, S} -> compile_tests(State, Apps, S, RawOpts); Error -> Error end end. +compile_tests(State, TestApps, Suites, RawOpts) -> + F = fun(AppInfo) -> + S = rebar_app_info:state_or_new(State, AppInfo), + ok = rebar_erlc_compiler:compile(replace_src_dirs(S), + ec_cnv:to_list(rebar_app_info:out_dir(AppInfo))) + end, + lists:foreach(F, TestApps), + 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)). @@ -204,8 +215,20 @@ app_modules([App|Rest], Acc) -> app_modules(Rest, NewAcc) end. -test_set(Apps, all) -> {ok, set_apps(Apps, [])}; -test_set(_Apps, Suites) -> {ok, set_suites(Suites, [])}. +replace_src_dirs(State) -> + %% replace any `src_dirs` with the test dirs + ErlOpts = rebar_state:get(State, erl_opts, []), + StrippedOpts = filter_src_dirs(ErlOpts), + case rebar_dir:extra_src_dirs(State) of + [] -> rebar_state:set(State, erl_opts, [{src_dirs, ["test"]}|StrippedOpts]); + _ -> rebar_state:set(State, erl_opts, StrippedOpts) + end. + +filter_src_dirs(ErlOpts) -> + lists:filter(fun({src_dirs, _}) -> false; (_) -> true end, ErlOpts). + +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) -> diff --git a/src/rebar_prv_help.erl b/src/rebar_prv_help.erl index be5717f..c028264 100644 --- a/src/rebar_prv_help.erl +++ b/src/rebar_prv_help.erl @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 help <task>"}, {short_desc, "Display a list of tasks or help for a given task or subtask."}, @@ -65,7 +65,13 @@ task_help(Namespace, Name, State) -> Providers = rebar_state:providers(State), case providers:get_provider(Name, Providers, Namespace) of not_found -> - {error, io_lib:format("Unknown task ~p", [Name])}; + case providers:get_providers_by_namespace(Name, Providers) of + [] -> + {error, io_lib:format("Unknown task ~p", [Name])}; + NSProviders -> + providers:help(NSProviders), + {ok, State} + end; Provider -> providers:help(Provider), {ok, State} diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index ba49532..768d41a 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -37,7 +37,10 @@ -export([handle_deps/3, handle_deps/4, - handle_deps/5]). + handle_deps/5, + + find_cycles/1, + cull_compile/2]). -export_type([dep/0]). @@ -58,7 +61,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, true}, + {bare, false}, {deps, ?DEPS}, {example, undefined}, {short_desc, ""}, @@ -76,6 +79,10 @@ do(State) -> {Apps, State1} = lists:foldl(fun deps_per_profile/2, {[], State}, lists:reverse(Profiles)), + State2 = rebar_state:update_all_deps(State1, Apps), + CodePaths = [rebar_app_info:ebin_dir(A) || A <- Apps], + State3 = rebar_state:update_code_paths(State2, all_deps, CodePaths), + Source = ProjectApps ++ Apps, case find_cycles(Source) of {cycles, Cycles} -> @@ -84,7 +91,7 @@ do(State) -> {error, Error}; {no_cycle, Sorted} -> ToCompile = cull_compile(Sorted, ProjectApps), - {ok, rebar_state:deps_to_build(State1, ToCompile)} + {ok, rebar_state:deps_to_build(State3, ToCompile)} end catch %% maybe_fetch will maybe_throw an exception to break out of some loops @@ -158,11 +165,7 @@ handle_deps(Profile, State0, Deps, Upgrade, Locks) -> ,lists:ukeysort(2, SrcApps) ,lists:ukeysort(2, Solved)), - State5 = rebar_state:update_all_deps(State4, AllDeps), - CodePaths = [rebar_app_info:ebin_dir(A) || A <- AllDeps], - State6 = rebar_state:update_code_paths(State5, all_deps, CodePaths), - - {ok, AllDeps, State6}. + {ok, AllDeps, State4}. %% =================================================================== %% Internal functions @@ -202,31 +205,40 @@ update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State, Locks) update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State, Locks) end. +pkg_locked({Name, _, _}, Locks) -> + pkg_locked(Name, Locks); pkg_locked({Name, _}, Locks) -> + pkg_locked(Name, Locks); +pkg_locked(Name, Locks) -> false =/= lists:keyfind(Name, 1, Locks). -update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, _Locks) -> +update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, Locks) -> %% Create app_info record for each pkg dep DepsDir = profile_dep_dir(State, Profile), {Solved, _, State1} = lists:foldl(fun(Pkg, {Acc, SeenAcc, StateAcc}) -> - handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Acc, SeenAcc, StateAcc) + handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Acc, SeenAcc, Locks, StateAcc) end, {[], Seen, State}, Pkgs), {Solved, State1}. -handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, State) -> - AppInfo = package_to_app(DepsDir, Packages, Pkg, State), +handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, Locks, State) -> + IsLock = pkg_locked(Pkg, Locks), + AppInfo = package_to_app(DepsDir, Packages, Pkg, IsLock, State), + Deps = rebar_app_info:deps(AppInfo), Level = rebar_app_info:dep_level(AppInfo), {NewSeen, NewState} = maybe_lock(Profile, AppInfo, Seen, State, Level), {_, AppInfo1} = maybe_fetch(AppInfo, Profile, Upgrade, Seen, NewState), - {[AppInfo1 | Fetched], NewSeen, NewState}. + {AppInfo2, _, _, _, _} = + handle_dep(NewState, Profile, DepsDir, AppInfo1, Locks, Level), + AppInfo3 = rebar_app_info:deps(AppInfo2, Deps), + {[AppInfo3 | Fetched], NewSeen, NewState}. maybe_lock(Profile, AppInfo, Seen, State, Level) -> + Name = rebar_app_info:name(AppInfo), 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 -> Locks = rebar_state:lock(State), @@ -241,13 +253,13 @@ maybe_lock(Profile, AppInfo, Seen, State, Level) -> {Seen, State} end; _ -> - {Seen, State} + {sets:add_element(Name, Seen), State} end; true -> - {Seen, State} + {sets:add_element(Name, Seen), State} end. -package_to_app(DepsDir, Packages, {Name, Vsn, Level}, State) -> +package_to_app(DepsDir, Packages, {Name, Vsn, Level}, IsLock, State) -> case dict:find({Name, Vsn}, Packages) of error -> case rebar_packages:check_registry(Name, Vsn, State) of @@ -256,14 +268,13 @@ package_to_app(DepsDir, Packages, {Name, Vsn, Level}, State) -> false -> throw(?PRV_ERROR({missing_package, Name, Vsn})) end; - {ok, P} -> - PkgDeps = [{PkgName, PkgVsn} - || {PkgName,PkgVsn} <- proplists:get_value(<<"deps">>, P, [])], - {ok, AppInfo} = rebar_app_info:new(Name, Vsn), - AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps), - AppInfo2 = rebar_app_info:dir(AppInfo1, filename:join([DepsDir, Name])), - AppInfo3 = rebar_app_info:dep_level(AppInfo2, Level), - rebar_app_info:source(AppInfo3, {pkg, Name, Vsn}) + {ok, PkgDeps} -> + Source = {pkg, Name, Vsn}, + AppInfo = new_dep(DepsDir, Name, Vsn, Source, IsLock, State), + AppInfo1 = rebar_app_info:dep_level(rebar_app_info:deps(AppInfo, PkgDeps), Level), + BaseDir = rebar_state:get(State, base_dir, []), + AppState1 = rebar_state:set(rebar_app_info:state(AppInfo1), base_dir, BaseDir), + rebar_app_info:state(AppInfo1, AppState1) end. -spec update_src_deps(atom(), non_neg_integer(), list(), list(), list(), rebar_state:t(), boolean(), sets:set(binary()), list()) -> {rebar_state:t(), list(), list(), sets:set(binary())}. @@ -345,7 +356,6 @@ handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) {true, AppInfo1} -> handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks); - {false, AppInfo1} -> {[AppInfo1|SrcDeps], PkgDeps, SrcApps, State, Locks} end; @@ -356,7 +366,7 @@ handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> DepsDir = profile_dep_dir(State, Profile), {AppInfo1, NewSrcDeps, NewPkgDeps, NewLocks, State1} = - handle_dep(State, DepsDir, AppInfo, Locks, Level), + handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level), AppInfo2 = rebar_app_info:dep_level(AppInfo1, Level), {NewSrcDeps ++ SrcDeps ,NewPkgDeps++PkgDeps @@ -364,9 +374,9 @@ handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> ,State1 ,NewLocks}. --spec handle_dep(rebar_state:t(), file:filename_all(), rebar_app_info:t(), list(), integer()) -> +-spec handle_dep(rebar_state:t(), atom(), file:filename_all(), rebar_app_info:t(), list(), integer()) -> {rebar_app_info:t(), [rebar_app_info:t()], [pkg_dep()], [integer()]}. -handle_dep(State, DepsDir, AppInfo, Locks, Level) -> +handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) -> Profiles = rebar_state:current_profiles(State), Name = rebar_app_info:name(AppInfo), @@ -374,22 +384,26 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) -> S = rebar_app_info:state(AppInfo), S1 = rebar_state:new(S, C, rebar_app_info:dir(AppInfo)), - S2 = rebar_state:apply_profiles(S1, Profiles), - S3 = rebar_state:apply_overrides(S2, Name), - AppInfo1 = rebar_app_info:state(AppInfo, S3), + S2 = rebar_state:apply_overrides(S1, Name), + + S3 = rebar_state:apply_profiles(S2, Profiles), + Plugins = rebar_state:get(S3, plugins, []), + S4 = rebar_state:set(S3, {plugins, Profile}, Plugins), + AppInfo1 = rebar_app_info:state(AppInfo, S4), %% Dep may have plugins to install. Find and install here. - State1 = rebar_plugins:handle_plugins(rebar_state:get(S3, plugins, []), State), + S5 = rebar_plugins:install(S4), + AppInfo2 = rebar_app_info:state(AppInfo1, S5), - Deps = rebar_state:get(S3, deps, []), %% Upgrade lock level to be the level the dep will have in this dep tree + Deps = rebar_state:get(S5, deps, []), NewLocks = [{DepName, Source, LockLevel+Level} || - {DepName, Source, LockLevel} <- rebar_state:get(S3, {locks, default}, [])], - AppInfo2 = rebar_app_info:deps(AppInfo1, rebar_state:deps_names(Deps)), - {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, S3, Locks, Level), - {AppInfo2, SrcDeps, PkgDeps, Locks++NewLocks, State1}. + {DepName, Source, LockLevel} <- rebar_state:get(S5, {locks, default}, [])], + AppInfo3 = rebar_app_info:deps(AppInfo2, rebar_state:deps_names(Deps)), + {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, S5, Locks, Level+1), + {AppInfo3, SrcDeps, PkgDeps, Locks++NewLocks, State}. --spec maybe_fetch(rebar_app_info:t(), atom(), boolean() | {true, binary(), integer()}, +-spec maybe_fetch(rebar_app_info:t(), atom(), boolean(), sets:set(binary()), rebar_state:t()) -> {boolean(), rebar_app_info:t()}. maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) -> AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)), @@ -405,7 +419,7 @@ maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) -> case fetch_app(AppInfo, AppDir, State) of true -> maybe_symlink_default(State, Profile, AppDir, AppInfo), - {true, update_app_info(AppInfo)}; + {true, update_app_info(AppDir, AppInfo)}; Other -> {Other, AppInfo} end; @@ -432,7 +446,7 @@ maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) -> already_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]), + DefaultAppDir = filename:join([rebar_state:get(State, base_dir, []), "default", "lib", Name]), rebar_app_discover:find_app(DefaultAppDir, all). needs_symlinking(State, Profile) -> @@ -477,69 +491,69 @@ parse_deps(DepsDir, Deps, State, Locks, Level) -> end, case lists:keyfind(ec_cnv:to_binary(Name), 1, Locks) of false -> - parse_dep(Dep, Acc, DepsDir, State); + parse_dep(Dep, Acc, DepsDir, false, State); LockedDep -> LockedLevel = element(3, LockedDep), case LockedLevel > Level of true -> - parse_dep(Dep, Acc, DepsDir, State); + parse_dep(Dep, Acc, DepsDir, false, State); false -> - parse_dep(LockedDep, Acc, DepsDir, State) + parse_dep(LockedDep, Acc, DepsDir, true, State) end end end, {[], []}, Deps). -parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_list(Vsn) -> +parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, 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 = new_dep(DepsDir, Name, [], [], IsLock, 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) -> +parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_atom(Name) -> {PkgName, PkgVsn} = get_package(ec_cnv:to_binary(Name), State), 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 = new_dep(DepsDir, Name, [], [], IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; not_found -> {SrcDepsAcc, [{PkgName, PkgVsn} | PkgDepsAcc]} end; -parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> - Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> + Dep = new_dep(DepsDir, Name, [], Source, IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> - Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> + Dep = new_dep(DepsDir, Name, [], Source, IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, _Vsn, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> - Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, _Vsn, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> + Dep = new_dep(DepsDir, Name, [], Source, IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, _Vsn, Source, Opts}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> +parse_dep({Name, _Vsn, Source, Opts}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> ?WARN("Dependency option list ~p in ~p is not supported and will be ignored", [Opts, Name]), - Dep = new_dep(DepsDir, Name, [], Source, State), + Dep = new_dep(DepsDir, Name, [], Source, IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_integer(Level) -> +parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, 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 = new_dep(DepsDir, Name, [], [], IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; not_found -> {SrcDepsAcc, [{Name, Vsn} | PkgDepsAcc]} end; -parse_dep({Name, Source, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) +parse_dep({Name, Source, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) , is_integer(Level) -> - Dep = new_dep(DepsDir, Name, [], Source, State), + Dep = new_dep(DepsDir, Name, [], Source, IsLock, State), {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep(Dep, _, _, _) -> +parse_dep(Dep, _, _, _, _) -> throw(?PRV_ERROR({parse_dep, Dep})). -new_dep(DepsDir, Name, Vsn, Source, State) -> +new_dep(DepsDir, Name, Vsn, Source, IsLock, State) -> CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), {ok, Dep} = case rebar_app_info:discover(CheckoutsDir) of {ok, App} -> @@ -560,7 +574,7 @@ new_dep(DepsDir, Name, Vsn, Source, State) -> ParentOverrides = rebar_state:overrides(State), Dep1 = rebar_app_info:state(Dep, rebar_state:overrides(S, ParentOverrides++Overrides)), - rebar_app_info:source(Dep1, Source). + rebar_app_info:is_lock(rebar_app_info:source(Dep1, Source), IsLock). fetch_app(AppInfo, AppDir, State) -> ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]), @@ -572,8 +586,12 @@ fetch_app(AppInfo, AppDir, State) -> throw(Error) end. -update_app_info(AppInfo) -> - AppDetails = rebar_app_info:app_details(AppInfo), +%% This is called after the dep has been downloaded and unpacked, if it hadn't been already. +%% So this is the first time for newly downloaded apps that its .app/.app.src data can +%% be read in an parsed. +update_app_info(AppDir, AppInfo) -> + {ok, Found} = rebar_app_info:discover(AppDir), + AppDetails = rebar_app_info:app_details(Found), Applications = proplists:get_value(applications, AppDetails, []), IncludedApplications = proplists:get_value(included_applications, AppDetails, []), AppInfo1 = rebar_app_info:applications( @@ -581,22 +599,29 @@ update_app_info(AppInfo) -> IncludedApplications++Applications), rebar_app_info:valid(AppInfo1, false). -maybe_upgrade(AppInfo, AppDir, false, State) -> - Source = rebar_app_info:source(AppInfo), - rebar_fetch:needs_update(AppDir, Source, State); -maybe_upgrade(AppInfo, AppDir, true, State) -> +maybe_upgrade(AppInfo, AppDir, Upgrade, State) -> Source = rebar_app_info:source(AppInfo), - case rebar_fetch:needs_update(AppDir, Source, State) of + case Upgrade orelse rebar_app_info:is_lock(AppInfo) of true -> - ?INFO("Upgrading ~s", [rebar_app_info:name(AppInfo)]), - case rebar_fetch:download_source(AppDir, Source, State) of + case rebar_fetch:needs_update(AppDir, Source, State) of true -> - true; - Error -> - throw(Error) + ?INFO("Upgrading ~s", [rebar_app_info:name(AppInfo)]), + case rebar_fetch:download_source(AppDir, Source, State) of + true -> + true; + Error -> + throw(Error) + end; + false -> + case Upgrade of + true -> + ?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]), + false; + false -> + false + end end; false -> - ?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]), false end. @@ -632,7 +657,8 @@ warn_skip_pkg({Name, Source}, State) -> not_needs_compile(App) -> not(rebar_app_info:is_checkout(App)) - andalso rebar_app_info:valid(App). + andalso rebar_app_info:valid(App) + andalso rebar_state:has_all_artifacts(rebar_app_info:state(App)) =:= true. get_package(Dep, State) -> case rebar_state:registry(State) of diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl index 6fdd743..058846a 100644 --- a/src/rebar_prv_lock.erl +++ b/src/rebar_prv_lock.erl @@ -19,7 +19,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, true}, + {bare, false}, {deps, ?DEPS}, {example, ""}, {short_desc, "Locks dependencies."}, diff --git a/src/rebar_prv_new.erl b/src/rebar_prv_new.erl index 6bc9f53..0528f44 100644 --- a/src/rebar_prv_new.erl +++ b/src/rebar_prv_new.erl @@ -20,7 +20,7 @@ init(State) -> Provider = providers:create([ {name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 new <template>"}, {short_desc, "Create new project from templates."}, diff --git a/src/rebar_prv_packages.erl b/src/rebar_prv_packages.erl index c7c0d50..8ba66de 100644 --- a/src/rebar_prv_packages.erl +++ b/src/rebar_prv_packages.erl @@ -17,7 +17,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 pkgs"}, {short_desc, "List available packages."}, @@ -27,9 +27,9 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - case rebar_packages:registry(State) of - {ok, Registry} -> - print_packages(Registry), + case rebar_packages:get_packages(State) of + {Dict, _} -> + print_packages(Dict), {ok, State}; error -> ?PRV_ERROR(load_registry_fail) @@ -39,13 +39,16 @@ do(State) -> format_error(load_registry_fail) -> "Failed to load package regsitry. Try running 'rebar3 update' to fix". -print_packages(Table) -> - MS = ets:fun2ms(fun({Key, [Value]}) when is_binary(Key) -> {Key, Value} end), - Pkgs = ets:select(Table, MS), - lists:foreach(fun({Name, Vsns}) -> - VsnStr = join(Vsns, <<", ">>), - io:format("~s:~n Versions: ~s~n~n", [Name, VsnStr]) - end, Pkgs). +print_packages(Dict) -> + Pkgs = lists:keysort(1, dict:fetch_keys(Dict)), + SortedPkgs = lists:foldl(fun({Pkg, Vsn}, Acc) -> + orddict:append_list(Pkg, [Vsn], Acc) + end, orddict:new(), Pkgs), + + orddict:map(fun(Name, Vsns) -> + VsnStr = join(Vsns, <<", ">>), + io:format("~s:~n Versions: ~s~n~n", [Name, VsnStr]) + end, SortedPkgs). -spec join([binary()], binary()) -> binary(). join([Bin], _Sep) -> diff --git a/src/rebar_prv_plugins.erl b/src/rebar_prv_plugins.erl new file mode 100644 index 0000000..328958e --- /dev/null +++ b/src/rebar_prv_plugins.erl @@ -0,0 +1,67 @@ +-module(rebar_prv_plugins). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). + +-define(PROVIDER, list). +-define(NAMESPACE, plugins). +-define(DEPS, []). + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + State1 = rebar_state:add_provider( + State, + providers:create([ + {name, ?PROVIDER}, + {module, ?MODULE}, + {namespace, ?NAMESPACE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 plugins list"}, + {short_desc, "List local and global plugins for this project"}, + {desc, "List local and global plugins for this project"}, + {opts, []}])), + {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + GlobalConfigFile = rebar_dir:global_config(), + GlobalConfig = rebar_state:new(rebar_config:consult_file(GlobalConfigFile)), + GlobalPlugins = rebar_state:get(GlobalConfig, plugins, []), + GlobalPluginsDir = filename:join(rebar_dir:global_cache_dir(State), "plugins"), + display_plugins("Global plugins", GlobalPluginsDir, GlobalPlugins), + + Plugins = rebar_state:get(State, plugins, []), + PluginsDir =rebar_dir:plugins_dir(State), + display_plugins("Local plugins", PluginsDir, Plugins), + {ok, State}. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +display_plugins(_Header, _Dir, []) -> + ok; +display_plugins(Header, Dir, Plugins) -> + ?CONSOLE("--- ~s ---", [Header]), + display_plugins(Dir, Plugins), + ?CONSOLE("", []). + +display_plugins(Dir, Plugins) -> + lists:foreach(fun(Plugin) -> + Name = if is_atom(Plugin) -> Plugin; + is_tuple(Plugin) -> element(1, Plugin) + end, + case rebar_app_info:discover(filename:join(Dir, Name)) of + {ok, _App} -> + ?CONSOLE("~s", [Name]); + not_found -> + ?DEBUG("Unable to find plugin ~s", [Name]) + end + end, Plugins). diff --git a/src/rebar_prv_plugins_upgrade.erl b/src/rebar_prv_plugins_upgrade.erl new file mode 100644 index 0000000..5ccd054 --- /dev/null +++ b/src/rebar_prv_plugins_upgrade.erl @@ -0,0 +1,110 @@ +-module(rebar_prv_plugins_upgrade). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). + +-define(PROVIDER, upgrade). +-define(NAMESPACE, plugins). +-define(DEPS, []). + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + State1 = rebar_state:add_provider( + State, + providers:create([ + {name, ?PROVIDER}, + {module, ?MODULE}, + {namespace, ?NAMESPACE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 plugins upgrade <plugin>"}, + {short_desc, "Uprade plugins"}, + {desc, "List or upgrade plugins"}, + {opts, [{plugin, undefined, undefined, string, + "Plugin to upgrade"}]}])), + {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + {Args, _} = rebar_state:command_parsed_args(State), + case proplists:get_value(plugin, Args, none) of + none -> + ?PRV_ERROR(no_plugin_arg); + Plugin -> + upgrade(Plugin, State) + end. + +-spec format_error(any()) -> iolist(). +format_error(no_plugin_arg) -> + io_lib:format("Must give an installed plugin to upgrade as an argument", []); +format_error({not_found, Plugin}) -> + io_lib:format("Plugin to upgrade not found: ~s", [Plugin]); +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +upgrade(Plugin, State) -> + Profiles = rebar_state:current_profiles(State), + case find_plugin(Plugin, Profiles, State) of + not_found -> + Dep = find_plugin(Plugin, [global], State); + Dep -> + Dep + end, + + case Dep of + not_found -> + ?PRV_ERROR({not_found, Plugin}); + {ok, P, Profile} -> + State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR), + {ok, Apps, _State2} = rebar_prv_install_deps:handle_deps(Profile + ,State1 + ,[P] + ,true), + + {no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(Apps), + ToBuild = rebar_prv_install_deps:cull_compile(Sorted, []), + + %% Add already built plugin deps to the code path + CodePaths = [rebar_app_info:ebin_dir(A) || A <- Apps -- ToBuild], + code:add_pathsa(CodePaths), + + %% Build plugin and its deps + [build_plugin(AppInfo, Apps, State) || AppInfo <- ToBuild], + {ok, State} + end. + +find_plugin(Plugin, Profiles, State) -> + ec_lists:search(fun(Profile) -> + Plugins = rebar_state:get(State, {plugins, Profile}, []), + case find(list_to_atom(Plugin), Plugins) of + false -> + not_found; + P -> + {ok, P} + end + end, Profiles). + +find(_Plugin, []) -> + false; +find(Plugin, [Plugin | _Plugins]) -> + Plugin; +find(Plugin, [Plugin1 | Plugins]) when is_tuple(Plugin1) -> + case element(1, Plugin1) =:= Plugin of + true -> + Plugin1; + false -> + find(Plugin, Plugins) + end. + +build_plugin(AppInfo, Apps, State) -> + Providers = rebar_state:providers(State), + AppDir = rebar_app_info:dir(AppInfo), + C = rebar_config:consult(AppDir), + S = rebar_state:new(rebar_state:all_deps(rebar_state:new(), Apps), C, AppDir), + rebar_prv_compile:compile(S, Providers, AppInfo). diff --git a/src/rebar_prv_release.erl b/src/rebar_prv_release.erl index 595280c..2cf9b23 100644 --- a/src/rebar_prv_release.erl +++ b/src/rebar_prv_release.erl @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 release"}, {short_desc, "Build release of project."}, @@ -32,31 +32,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Caller = rebar_state:get(State, caller, api), - Options = rebar_state:command_args(State), - DepsDir = rebar_dir:deps_dir(State), - ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), - LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), - OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), - AllOptions = string:join(["release" | Options], " "), - try - case rebar_state:get(State, relx, []) of - [] -> - relx:main([{lib_dirs, LibDirs} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions); - Config -> - relx:main([{lib_dirs, LibDirs} - ,{config, lists:reverse(Config)} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions) - end, - {ok, State} - catch - throw:T -> - {error, {rlx_prv_release, T}} - end. + rebar_relx:do(rlx_prv_release, "release", ?PROVIDER, State). -spec format_error(any()) -> iolist(). format_error(Reason) -> diff --git a/src/rebar_prv_relup.erl b/src/rebar_prv_relup.erl new file mode 100644 index 0000000..a4cd8ae --- /dev/null +++ b/src/rebar_prv_relup.erl @@ -0,0 +1,40 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_relup). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, relup). +-define(DEPS, [release]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 relup"}, + {short_desc, "Create relup of releases."}, + {desc, "Create relup of releases."}, + {opts, relx:opt_spec_list()}]), + State1 = rebar_state:add_provider(State, Provider), + {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + rebar_relx:do(rlx_prv_release, "relup", ?PROVIDER, State). + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_report.erl b/src/rebar_prv_report.erl index e052998..587fad7 100644 --- a/src/rebar_prv_report.erl +++ b/src/rebar_prv_report.erl @@ -23,7 +23,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 report \"<task>\""}, {short_desc, "Provide a crash report to be sent to the rebar3 issues page."}, diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index ec2f692..84ad723 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -45,14 +45,25 @@ -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> - State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, - {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "rebar3 shell"}, - {short_desc, "Run shell with project apps and deps in path."}, - {desc, info()}, - {opts, [{config, undefined, "config", string, "Path to the config file to use. Defaults to the sys_config defined for relx, if present."}]}])), + State1 = rebar_state:add_provider( + State, + providers:create([ + {name, ?PROVIDER}, + {module, ?MODULE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 shell"}, + {short_desc, "Run shell with project apps and deps in path."}, + {desc, info()}, + {opts, [{config, undefined, "config", string, + "Path to the config file to use. Defaults to the " + "sys_config defined for relx, if present."}, + {name, undefined, "name", atom, + "Gives a long name to the node."}, + {sname, undefined, "sname", atom, + "Gives a short name to the node."}]} + ]) + ), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. @@ -72,6 +83,21 @@ format_error(Reason) -> %% immediately kill the script. ctrl-g, however, works fine shell(State) -> + setup_name(State), + setup_paths(State), + setup_shell(), + %% apps must be started after the change in shell because otherwise + %% their application masters never gets the new group leader (held in + %% their internal state) + maybe_boot_apps(State), + rebar_agent:start_link(State), + %% this call never returns (until user quits shell) + timer:sleep(infinity). + +info() -> + "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n". + +setup_shell() -> %% scan all processes for any with references to the old user and save them to %% update later NeedsUpdate = [Pid || Pid <- erlang:processes(), @@ -84,25 +110,100 @@ shell(State) -> %% wait until user_drv and user have been registered (max 3 seconds) ok = wait_until_user_started(3000), %% set any process that had a reference to the old user's group leader to the - %% new user process - _ = [erlang:group_leader(whereis(user), Pid) || Pid <- NeedsUpdate], - %% enable error_logger's tty output - ok = error_logger:swap_handler(tty), - %% disable the simple error_logger (which may have been added multiple - %% times). removes at most the error_logger added by init and the - %% error_logger added by the tty handler - ok = remove_error_handler(3), + %% new user process. Catch the race condition when the Pid exited after the + %% liveness check. + _ = [catch erlang:group_leader(whereis(user), Pid) || Pid <- NeedsUpdate, + is_process_alive(Pid)], + try + %% enable error_logger's tty output + error_logger:swap_handler(tty), + %% disable the simple error_logger (which may have been added multiple + %% times). removes at most the error_logger added by init and the + %% error_logger added by the tty handler + remove_error_handler(3) + catch + E:R -> % may fail with custom loggers + ?DEBUG("Logger changes failed for ~p:~p (~p)", [E,R,erlang:get_stacktrace()]), + hope_for_best + end. + +setup_paths(State) -> %% Add deps to path code:add_pathsa(rebar_state:code_paths(State, all_deps)), %% add project app test paths - ok = add_test_paths(State), - %% try to read in sys.config file - ok = reread_config(State), - %% this call never returns (until user quits shell) - timer:sleep(infinity). + ok = add_test_paths(State). -info() -> - "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n". +maybe_boot_apps(State) -> + case find_apps_to_boot(State) of + undefined -> + %% try to read in sys.config file + ok = reread_config(State); + Apps -> + %% load apps, then check config, then boot them. + load_apps(Apps), + ok = reread_config(State), + boot_apps(Apps) + end. + +setup_name(State) -> + {Opts, _} = rebar_state:command_parsed_args(State), + case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of + {undefined, undefined} -> + ok; + {Name, undefined} -> + net_kernel:start([Name, longnames]); + {undefined, SName} -> + net_kernel:start([SName, shortnames]); + {_, _} -> + ?ABORT("Cannot have both short and long node names defined", []) + end. + +find_apps_to_boot(State) -> + %% Try the shell_apps option + case rebar_state:get(State, shell_apps, undefined) of + undefined -> + %% Get to the relx tuple instead + case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of + {_, _, Apps} -> Apps; + false -> undefined + end; + Apps -> + Apps + end. + +load_apps(Apps) -> + [case application:load(App) of + ok -> + {ok, Ks} = application:get_all_key(App), + load_apps(proplists:get_value(applications, Ks)); + _ -> + error % will be caught when starting the app + end || App <- Apps, + not lists:keymember(App, 1, application:loaded_applications())], + ok. + +reread_config(State) -> + case find_config(State) of + no_config -> + ok; + ConfigList -> + _ = [application:set_env(Application, Key, Val) + || {Application, Items} <- ConfigList, + {Key, Val} <- Items], + ok + end. + +boot_apps(Apps) -> + ?WARN("The rebar3 shell is a development tool; to deploy " + "applications in production, consider using releases " + "(http://www.rebar3.org/v3.0/docs/releases)", []), + Res = [application:ensure_all_started(App) || App <- Apps], + _ = [?INFO("Booted ~p", [App]) + || {ok, Booted} <- Res, + App <- Booted], + _ = [?ERROR("Failed to boot ~p for reason ~p", [App, Reason]) + || {error, {App, Reason}} <- Res], + ok. remove_error_handler(0) -> ?WARN("Unable to remove simple error_logger handler", []); @@ -124,32 +225,16 @@ wait_until_user_started(Timeout) -> end. add_test_paths(State) -> - lists:foreach(fun(App) -> - AppDir = rebar_app_info:out_dir(App), - %% ignore errors resulting from non-existent directories - _ = code:add_path(filename:join([AppDir, "test"])) - end, rebar_state:project_apps(State)), + _ = [begin + AppDir = rebar_app_info:out_dir(App), + %% ignore errors resulting from non-existent directories + _ = code:add_path(filename:join([AppDir, "test"])) + end || App <- rebar_state:project_apps(State)], _ = code:add_path(filename:join([rebar_dir:base_dir(State), "test"])), ok. -reread_config(State) -> - case find_config(State) of - no_config -> - ok; - {ok, ConfigList} -> - lists:foreach(fun ({Application, Items}) -> - lists:foreach(fun ({Key, Val}) -> - application:set_env(Application, Key, Val) - end, - Items) - end, - ConfigList); - {error, Error} -> - ?ABORT("Error while attempting to read configuration file: ~p", [Error]) - end. - % First try the --config flag, then try the relx sys_config --spec find_config(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +-spec find_config(rebar_state:t()) -> [tuple()] | no_config. find_config(State) -> case find_config_option(State) of no_config -> @@ -158,7 +243,7 @@ find_config(State) -> Result end. --spec find_config_option(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +-spec find_config_option(rebar_state:t()) -> [tuple()] | no_config. find_config_option(State) -> {Opts, _} = rebar_state:command_parsed_args(State), case proplists:get_value(config, Opts) of @@ -168,7 +253,7 @@ find_config_option(State) -> consult_config(State, Filename) end. --spec find_config_relx(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +-spec find_config_relx(rebar_state:t()) -> [tuple()] | no_config. find_config_relx(State) -> case proplists:get_value(sys_config, rebar_state:get(State, relx, [])) of undefined -> @@ -181,11 +266,7 @@ find_config_relx(State) -> consult_config(State, Filename) -> Fullpath = filename:join(rebar_dir:root_dir(State), Filename), ?DEBUG("Loading configuration from ~p", [Fullpath]), - case file:consult(Fullpath) of - {ok, [Config]} -> - {ok, Config}; - {ok, []} -> - {ok, []}; - {error, Error} -> - {error, {Error, Fullpath}} + case rebar_file_utils:try_consult(Fullpath) of + [T] -> T; + [] -> [] end. diff --git a/src/rebar_prv_tar.erl b/src/rebar_prv_tar.erl index f7557bd..b3a12c0 100644 --- a/src/rebar_prv_tar.erl +++ b/src/rebar_prv_tar.erl @@ -12,7 +12,7 @@ -include("rebar.hrl"). -define(PROVIDER, tar). --define(DEPS, [compile]). +-define(DEPS, [release]). %% =================================================================== %% Public API @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 tar"}, {short_desc, "Tar archive of release built of project."}, @@ -32,26 +32,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Caller = rebar_state:get(State, caller, api), - Options = rebar_state:command_args(State), - DepsDir = rebar_dir:deps_dir(State), - ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), - LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), - OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), - AllOptions = string:join(["release", "tar" | Options], " "), - case rebar_state:get(State, relx, []) of - [] -> - relx:main([{lib_dirs, LibDirs} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions); - Config -> - relx:main([{lib_dirs, LibDirs} - ,{config, Config} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions) - end, - {ok, State}. + rebar_relx:do(rlx_prv_release, "tar", ?PROVIDER, State). -spec format_error(any()) -> iolist(). format_error(Reason) -> diff --git a/src/rebar_prv_unlock.erl b/src/rebar_prv_unlock.erl index 9790fcf..b049c92 100644 --- a/src/rebar_prv_unlock.erl +++ b/src/rebar_prv_unlock.erl @@ -22,7 +22,7 @@ init(State) -> State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, ""}, {short_desc, "Unlock dependencies."}, diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index dfb719a..6838bab 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -23,7 +23,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 update"}, {short_desc, "Update package index."}, diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index 05845e4..49d5674 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -28,7 +28,7 @@ init(State) -> rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 upgrade [cowboy[,ranch]]"}, {short_desc, "Upgrade dependencies."}, @@ -94,10 +94,15 @@ prepare_locks([Name|Names], Deps, Locks, Unlocks) -> AtomName = binary_to_atom(Name, utf8), case lists:keyfind(Name, 1, Locks) of {_, _, 0} = Lock -> - case lists:keyfind(AtomName, 1, Deps) of - false -> + case {lists:keyfind(AtomName, 1, Deps), lists:member(AtomName, Deps)} of + {false, false} -> ?PRV_ERROR({unknown_dependency, Name}); - Dep -> + {Dep, false} -> + {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks), + prepare_locks(Names, Deps, NewLocks, + [{Name, Source, 0} | NewUnlocks ++ Unlocks]); + {false, true} -> % package as a single atom + Dep = AtomName, {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks), prepare_locks(Names, Deps, NewLocks, [{Name, Source, 0} | NewUnlocks ++ Unlocks]) @@ -116,9 +121,13 @@ prepare_locks([Name|Names], Deps, Locks, Unlocks) -> end. prepare_lock(Dep, Lock, Locks) -> - Source = Source = case Dep of - {_, Src} -> Src; - {_, _, Src} -> Src + Source = case Dep of + {_, SrcOrVsn} -> SrcOrVsn; + {_, _, Src} -> Src; + _ when is_atom(Dep) -> + %% version-free package. Must unlock whatever matches in locks + {_, Vsn, _} = lists:keyfind(ec_cnv:to_binary(Dep), 1, Locks), + Vsn end, {NewLocks, NewUnlocks} = unlock_higher_than(0, Locks -- [Lock]), {Source, NewLocks, NewUnlocks}. diff --git a/src/rebar_prv_version.erl b/src/rebar_prv_version.erl index 5edcc85..bcc5f6c 100644 --- a/src/rebar_prv_version.erl +++ b/src/rebar_prv_version.erl @@ -22,7 +22,7 @@ init(State) -> State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, + {bare, true}, {deps, ?DEPS}, {example, "rebar3 version"}, {short_desc, "Print version for rebar and current Erlang."}, diff --git a/src/rebar_prv_xref.erl b/src/rebar_prv_xref.erl index ed53da6..623e946 100644 --- a/src/rebar_prv_xref.erl +++ b/src/rebar_prv_xref.erl @@ -27,7 +27,7 @@ init(State) -> Provider = providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {deps, ?DEPS}, - {bare, false}, + {bare, true}, {example, "rebar3 xref"}, {short_desc, short_desc()}, {desc, desc()}]), diff --git a/src/rebar_relx.erl b/src/rebar_relx.erl new file mode 100644 index 0000000..a3adedd --- /dev/null +++ b/src/rebar_relx.erl @@ -0,0 +1,69 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_relx). + +-export([do/4, + format_error/1]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec do(atom(), string(), atom(), rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(Module, Command, Provider, State) -> + Options = rebar_state:command_args(State), + DepsDir = rebar_dir:deps_dir(State), + ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), + LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, + [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), + OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), + AllOptions = string:join([Command | Options], " "), + Cwd = rebar_state:dir(State), + Providers = rebar_state:providers(State), + rebar_hooks:run_all_hooks(Cwd, pre, Provider, Providers, State), + try + case rebar_state:get(State, relx, []) of + [] -> + relx:main([{lib_dirs, LibDirs} + ,{output_dir, OutputDir} + ,{caller, api}], AllOptions); + Config -> + Config1 = update_config(Config), + relx:main([{lib_dirs, LibDirs} + ,{config, Config1} + ,{output_dir, OutputDir} + ,{caller, api}], AllOptions) + end, + rebar_hooks:run_all_hooks(Cwd, post, Provider, Providers, State), + {ok, State} + catch + throw:T -> + {error, {Module, T}} + end. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +%% To handle profiles rebar3 expects the provider to use the first entry +%% in a configuration key-value list as the value of a key if dups exist. +%% This does not work with relx. Some config options must not lose their +%% order (release which has an extends option is one). So here we pull out +%% options that are special so we can reverse the rest so what we expect +%% from a rebar3 profile is what we get on the relx side. +-define(SPECIAL_KEYS, [release, vm_args, sys_config, overlay_vars, lib_dirs]). + +update_config(Config) -> + {Special, Other} = + lists:foldl(fun(Tuple, {SpecialAcc, OtherAcc}) when is_tuple(Tuple) -> + case lists:member(element(1, Tuple), ?SPECIAL_KEYS) of + true -> + {[Tuple | SpecialAcc], OtherAcc}; + false -> + {SpecialAcc, [Tuple | OtherAcc]} + end + end, {[], []}, Config), + lists:reverse(Special) ++ Other. diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 7a6e60d..d0b28de 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -1,8 +1,13 @@ -module(rebar_state). -export([new/0, new/1, new/2, new/3, + get/2, get/3, set/3, + format_error/1, + + has_all_artifacts/1, + code_paths/2, code_paths/3, update_code_paths/3, opts/1, opts/2, @@ -12,7 +17,7 @@ lock/1, lock/2, - current_profiles/1, + current_profiles/1, current_profiles/2, command_args/1, command_args/2, command_parsed_args/1, command_parsed_args/2, @@ -24,6 +29,7 @@ project_apps/1, project_apps/2, deps_to_build/1, deps_to_build/2, + all_plugin_deps/1, all_plugin_deps/2, update_all_plugin_deps/2, all_deps/1, all_deps/2, update_all_deps/2, namespace/1, namespace/2, @@ -39,6 +45,7 @@ providers/1, providers/2, add_provider/2]). -include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). -record(state_t, {dir :: file:name(), opts = dict:new() :: rebar_dict(), @@ -48,13 +55,14 @@ lock = [], current_profiles = [default] :: [atom()], - namespace = undefined :: atom(), + namespace = default :: atom(), command_args = [], - command_parsed_args = [], + command_parsed_args = {[], []}, project_apps = [] :: [rebar_app_info:t()], deps_to_build = [] :: [rebar_app_info:t()], + all_plugin_deps = [] :: [rebar_app_info:t()], all_deps = [] :: [rebar_app_info:t()], packages = undefined :: {rebar_dict(), rebar_digraph()} | undefined, @@ -76,7 +84,10 @@ new() -> new(Config) when is_list(Config) -> BaseState = base_state(), Deps = proplists:get_value(deps, Config, []), - Opts = dict:from_list([{{deps, default}, Deps} | Config]), + Plugins = proplists:get_value(plugins, Config, []), + Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins} | Config], + true = rebar_config:verify_config_format(Terms), + Opts = dict:from_list(Terms), BaseState#state_t { dir = rebar_dir:get_cwd(), default = Opts, opts = Opts }. @@ -86,7 +97,11 @@ new(Profile, Config) when is_atom(Profile) , is_list(Config) -> BaseState = base_state(), Deps = proplists:get_value(deps, Config, []), - Opts = dict:from_list([{{deps, default}, Deps} | Config]), + + Plugins = proplists:get_value(plugins, Config, []), + Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins} | Config], + true = rebar_config:verify_config_format(Terms), + Opts = dict:from_list(Terms), BaseState#state_t { dir = rebar_dir:get_cwd(), current_profiles = [Profile], default = Opts, @@ -99,15 +114,21 @@ new(ParentState=#state_t{}, Config) -> -spec new(t(), list(), file:name()) -> t(). new(ParentState, Config, Dir) -> Opts = ParentState#state_t.opts, - LocalOpts = case rebar_config:consult_file(filename:join(Dir, ?LOCK_FILE)) of + LocalOpts = case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of [D] -> %% We want the top level deps only from the lock file. %% This ensures deterministic overrides for configs. Deps = [X || X <- D, element(3, X) =:= 0], - dict:from_list([{{locks, default}, D}, {{deps, default}, Deps} | Config]); + Plugins = proplists:get_value(plugins, Config, []), + Terms = [{{locks, default}, D}, {{deps, default}, Deps}, {{plugins, default}, Plugins} | Config], + true = rebar_config:verify_config_format(Terms), + dict:from_list(Terms); _ -> D = proplists:get_value(deps, Config, []), - dict:from_list([{{deps, default}, D} | Config]) + Plugins = proplists:get_value(plugins, Config, []), + Terms = [{{deps, default}, D}, {{plugins, default}, Plugins} | Config], + true = rebar_config:verify_config_format(Terms), + dict:from_list(Terms) end, NewOpts = merge_opts(LocalOpts, Opts), @@ -147,6 +168,26 @@ default(#state_t{default=Opts}) -> default(State, Opts) -> State#state_t{default=Opts}. +format_error({profile_not_list, Profile, Other}) -> + io_lib:format("Profile config must be a list but for profile '~p' config given as:~n~p", [Profile, Other]). + +-spec has_all_artifacts(rebar_app_info:t()) -> true | providers:error(). +has_all_artifacts(State) -> + Artifacts = rebar_state:get(State, artifacts, []), + Dir = rebar_dir:base_dir(State), + all(Dir, Artifacts). + +all(_, []) -> + true; +all(Dir, [File|Artifacts]) -> + case filelib:is_regular(filename:join(Dir, File)) of + false -> + ?DEBUG("Missing artifact ~s", [filename:join(Dir, File)]), + {false, File}; + true -> + all(Dir, Artifacts) + end. + code_paths(#state_t{code_paths=CodePaths}, Key) -> case dict:find(Key, CodePaths) of {ok, CodePath} -> @@ -175,6 +216,9 @@ opts(State, Opts) -> current_profiles(#state_t{current_profiles=Profiles}) -> Profiles. +current_profiles(State, Profiles) -> + State#state_t{current_profiles=Profiles}. + lock(#state_t{lock=Lock}) -> Lock. @@ -221,14 +265,17 @@ apply_overrides(State=#state_t{overrides=Overrides}, AppName) -> StateAcc end, State1, Overrides), - lists:foldl(fun({add, N, O}, StateAcc) when N =:= Name -> + State3 = lists:foldl(fun({add, N, O}, StateAcc) when N =:= Name -> lists:foldl(fun({Key, Value}, StateAcc1) -> OldValue = rebar_state:get(StateAcc1, Key, []), rebar_state:set(StateAcc1, Key, Value++OldValue) end, StateAcc, O); (_, StateAcc) -> StateAcc - end, State2, Overrides). + end, State2, Overrides), + + Opts = opts(State3), + State3#state_t{default=Opts}. add_to_profile(State, Profile, KVs) when is_atom(Profile), is_list(KVs) -> Profiles = rebar_state:get(State, profiles, []), @@ -244,12 +291,18 @@ apply_profiles(State, [default]) -> apply_profiles(State=#state_t{default = Defaults, current_profiles=CurrentProfiles}, Profiles) -> AppliedProfiles = deduplicate(CurrentProfiles ++ Profiles), ConfigProfiles = rebar_state:get(State, profiles, []), + NewOpts = lists:foldl(fun(default, OptsAcc) -> OptsAcc; (Profile, OptsAcc) -> - ProfileOpts = dict:from_list(proplists:get_value(Profile, ConfigProfiles, [])), - merge_opts(Profile, ProfileOpts, OptsAcc) + case proplists:get_value(Profile, ConfigProfiles, []) of + OptsList when is_list(OptsList) -> + ProfileOpts = dict:from_list(OptsList), + merge_opts(Profile, ProfileOpts, OptsAcc); + Other -> + throw(?PRV_ERROR({profile_not_list, Profile, Other})) + end end, Defaults, AppliedProfiles), State#state_t{current_profiles = AppliedProfiles, opts=NewOpts}. @@ -267,11 +320,18 @@ do_deduplicate([Head | Rest], Acc) -> merge_opts(Profile, NewOpts, OldOpts) -> Opts = merge_opts(NewOpts, OldOpts), - case dict:find(deps, NewOpts) of + Opts2 = case dict:find(plugins, NewOpts) of {ok, Value} -> - dict:store({deps, Profile}, Value, Opts); + dict:store({plugins, Profile}, Value, Opts); error -> Opts + end, + + case dict:find(deps, NewOpts) of + {ok, Value2} -> + dict:store({deps, Profile}, Value2, Opts2); + error -> + Opts2 end. merge_opts(NewOpts, OldOpts) -> @@ -279,6 +339,10 @@ merge_opts(NewOpts, OldOpts) -> NewValue; ({deps, _}, NewValue, _OldValue) -> NewValue; + (plugins, NewValue, _OldValue) -> + NewValue; + ({plugins, _}, NewValue, _OldValue) -> + NewValue; (profiles, NewValue, OldValue) -> dict:to_list(merge_opts(dict:from_list(NewValue), dict:from_list(OldValue))); (_Key, NewValue, OldValue) when is_list(NewValue) -> @@ -344,6 +408,15 @@ all_deps(#state_t{all_deps=Apps}) -> all_deps(State=#state_t{}, NewApps) -> State#state_t{all_deps=NewApps}. +all_plugin_deps(#state_t{all_plugin_deps=Apps}) -> + Apps. + +all_plugin_deps(State=#state_t{}, NewApps) -> + State#state_t{all_plugin_deps=NewApps}. + +update_all_plugin_deps(State=#state_t{all_plugin_deps=Apps}, NewApps) -> + State#state_t{all_plugin_deps=Apps++NewApps}. + update_all_deps(State=#state_t{all_deps=Apps}, NewApps) -> State#state_t{all_deps=Apps++NewApps}. diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 353fa36..3aa6e90 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -380,4 +380,4 @@ write_file(Output, Data, Force) -> %% Render a binary to a string, using mustache and the specified context %% render(Bin, Context) -> - mustache:render(ec_cnv:to_binary(Bin), Context, [{key_type, atom}]). + bbmustache:render(ec_cnv:to_binary(Bin), Context, [{key_type, atom}]). diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 160d547..0cbc7c2 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -47,6 +47,8 @@ deprecated/4, erl_opts/1, indent/1, + update_code/1, + remove_from_code_path/1, cleanup_code_path/1, args_to_tasks/1, expand_env_variable/3, @@ -152,7 +154,7 @@ sh(Command0, Options0) -> Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))), PortSettings = proplists:get_all_values(port_settings, Options) ++ - [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof], ?DEBUG("Port Cmd: ~s\nPort Opts: ~p\n", [Command, PortSettings]), Port = open_port({spawn, Command}, PortSettings), @@ -433,10 +435,14 @@ sh_loop(Port, Fun, Acc) -> sh_loop(Port, Fun, Fun(Line ++ "\n", Acc)); {Port, {data, {noeol, Line}}} -> sh_loop(Port, Fun, Fun(Line, Acc)); - {Port, {exit_status, 0}} -> - {ok, lists:flatten(lists:reverse(Acc))}; - {Port, {exit_status, Rc}} -> - {error, {Rc, lists:flatten(lists:reverse(Acc))}} + {Port, eof} -> + Data = lists:flatten(lists:reverse(Acc)), + receive + {Port, {exit_status, 0}} -> + {ok, Data}; + {Port, {exit_status, Rc}} -> + {error, {Rc, Data}} + end end. beam_to_mod(Dir, Filename) -> @@ -563,6 +569,39 @@ filter_defines([Opt | Rest], Acc) -> indent(Amount) when erlang:is_integer(Amount) -> [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. +%% Replace code paths with new paths for existing apps and +%% purge code of the old modules from those apps. +update_code(Paths) -> + lists:foreach(fun(Path) -> + Name = filename:basename(Path, "/ebin"), + App = list_to_atom(Name), + application:load(App), + case application:get_key(App, modules) of + undefined -> + code:add_patha(Path), + ok; + {ok, Modules} -> + code:replace_path(Name, Path), + [begin code:purge(M), code:delete(M) end || M <- Modules] + end + end, Paths). + +remove_from_code_path(Paths) -> + lists:foreach(fun(Path) -> + Name = filename:basename(Path, "/ebin"), + App = list_to_atom(Name), + application:load(App), + case application:get_key(App, modules) of + undefined -> + application:unload(App), + ok; + {ok, Modules} -> + application:unload(App), + [begin code:purge(M), code:delete(M) end || M <- Modules] + end, + code:del_path(Path) + end, Paths). + cleanup_code_path(OrigPath) -> CurrentPath = code:get_path(), AddedPaths = CurrentPath -- OrigPath, |