diff options
Diffstat (limited to 'src')
30 files changed, 701 insertions, 169 deletions
| diff --git a/src/rebar.app.src b/src/rebar.app.src index 58fee02..bd0f871 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -66,6 +66,7 @@                       rebar_prv_relup,                       rebar_prv_report,                       rebar_prv_shell, +                     rebar_prv_state,                       rebar_prv_tar,                       rebar_prv_unlock,                       rebar_prv_update, diff --git a/src/rebar.hrl b/src/rebar.hrl index f4e7f5e..0b7f0b1 100644 --- a/src/rebar.hrl +++ b/src/rebar.hrl @@ -22,7 +22,7 @@  -define(DEFAULT_TEST_DEPS_DIR, "test/lib").  -define(DEFAULT_RELEASE_DIR, "rel").  -define(DEFAULT_CONFIG_FILE, "rebar.config"). --define(DEFAULT_CDN, "https://s3.amazonaws.com/s3.hex.pm/"). +-define(DEFAULT_CDN, "https://repo.hex.pm/").  -define(REMOTE_PACKAGE_DIR, "tarballs").  -define(REMOTE_REGISTRY_FILE, "registry.ets.gz").  -define(LOCK_FILE, "rebar.lock"). diff --git a/src/rebar3.erl b/src/rebar3.erl index 879378e..ff0ab6a 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -126,14 +126,23 @@ run_aux(State, RawArgs) ->      {ok, Providers} = application:get_env(rebar, providers),      %% Providers can modify profiles stored in opts, so set default after initializing providers      State5 = rebar_state:create_logic_providers(Providers, State4), -    State6 = rebar_plugins:top_level_install(State5), -    State7 = rebar_state:default(State6, rebar_state:opts(State6)), +    %% Initializing project_plugins which can override default providers +    State6 = rebar_plugins:project_plugins_install(State5), +    State7 = rebar_plugins:top_level_install(State6), +    State8 = case os:getenv("REBAR_CACHE_DIR") of +                false -> +                    State7; +                ConfigFile -> +                    rebar_state:set(State7, global_rebar_dir, ConfigFile) +            end, + +    State9 = rebar_state:default(State8, rebar_state:opts(State8)),      {Task, Args} = parse_args(RawArgs), -    State8 = rebar_state:code_paths(State7, default, code:get_path()), +    State10 = rebar_state:code_paths(State9, default, code:get_path()), -    rebar_core:init_command(rebar_state:command_args(State8, Args), Task). +    rebar_core:init_command(rebar_state:command_args(State10, Args), Task).  init_config() ->      %% Initialize logging system @@ -250,10 +259,10 @@ set_global_flag(State, Options, Flag) ->  %%  global_option_spec_list() ->      [ -    %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} -    {help,     $h, "help",     undefined, "Print this help."}, -    {version,  $v, "version",  undefined, "Show version information."}, -    {task,     undefined, undefined, string, "Task to run."} +    %% {Name,  ShortOpt,  LongOpt,    ArgSpec,   HelpMsg} +    {help,     $h,        "help",     undefined, "Print this help."}, +    {version,  $v,        "version",  undefined, "Show version information."}, +    {task,     undefined, undefined,  string,    "Task to run."}      ].  handle_error(rebar_abort) -> diff --git a/src/rebar_agent.erl b/src/rebar_agent.erl index 2b69812..95818d8 100644 --- a/src/rebar_agent.erl +++ b/src/rebar_agent.erl @@ -86,6 +86,8 @@ refresh_paths(RState) ->                       || App <- rebar_state:project_apps(RState)]                  %% make sure to never reload self; halt()s the VM                  ) -- [filename:dirname(code:which(?MODULE))], +    %% Modules from apps we can't reload without breaking functionality +    Blacklist = [ec_cmd_log, providers, cf, cth_readable],      %% 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' @@ -102,11 +104,16 @@ refresh_paths(RState) ->                  undefined ->                      code:add_patha(Path),                      ok; -                {ok, _} -> -                    ?DEBUG("reloading ~p from ~s", [Modules, Path]), -                    code:replace_path(App, Path), -                    [begin code:purge(M), code:delete(M), code:load_file(M) end -                    || M <- Modules] +                {ok, Mods} -> +                    case {length(Mods), length(Mods -- Blacklist)} of +                        {X,X} -> +                            ?DEBUG("reloading ~p from ~s", [Modules, Path]), +                            code:replace_path(App, Path), +                            [begin code:purge(M), code:delete(M), code:load_file(M) end +                             || M <- Modules]; +                        {_,_} -> +                            ?DEBUG("skipping app ~p, stable copy required", [App]) +                    end              end          end, ToRefresh). diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 9fee4e0..cf3b82e 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -165,13 +165,13 @@ update_opts(AppInfo, Opts, Config) ->  deps_from_config(Dir, Config) ->      case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of -        [D] -> +        [] -> +            [{{deps, default}, proplists:get_value(deps, Config, [])}]; +        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], -            [{{locks, default}, D}, {{deps, default}, Deps}]; -        _ -> -            [{{deps, default}, proplists:get_value(deps, Config, [])}] +            [{{locks, default}, D}, {{deps, default}, Deps}]      end.  %% @doc discover a complete version of the app info with all fields set. diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 61301cb..8d7bcf4 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -30,6 +30,7 @@          ,consult_app_file/1          ,consult_file/1          ,consult_lock_file/1 +        ,write_lock_file/2          ,verify_config_format/1          ,format_error/1 @@ -50,7 +51,40 @@ consult_app_file(File) ->      consult_file_(File).  consult_lock_file(File) -> -    consult_file_(File). +    Terms = consult_file_(File), +    case Terms of +        [] -> +            []; +        [Locks] when is_list(Locks) -> % beta lock file +            Locks; +        [{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file +            %% Make sure the warning above is to be shown whenever a version +            %% newer than the current one is being used, as we can't parse +            %% all the contents of the lock file properly. +            ?WARN("Rebar3 detected a lock file from a newer version. " +                  "It will be loaded in compatibility mode, but important " +                  "information may be missing or lost. It is recommended to " +                  "upgrade Rebar3.", []), +            read_attrs(Vsn, Locks, Attrs) +    end. + +write_lock_file(LockFile, Locks) -> +    NewLocks = write_attrs(Locks), +    %% Write locks in the beta format, at least until it's been long +    %% enough we can start modifying the lock format. +    file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])). + +read_attrs(_Vsn, Locks, _Attrs) -> +    %% Beta copy does not know how to expand attributes, but +    %% is ready to support it. +    Locks. + +write_attrs(Locks) -> +    %% No attribute known that needs to be taken out of the structure, +    %% just return terms as is. +    Locks. + +  consult_file(File) ->      Terms = consult_file_(File), @@ -87,7 +121,7 @@ verify_config_format([Term | _]) ->  merge_locks(Config, []) ->      Config;  %% lockfile with entries -merge_locks(Config, [Locks]) -> +merge_locks(Config, Locks) ->      ConfigDeps = proplists:get_value(deps, Config, []),      %% We want the top level deps only from the lock file.      %% This ensures deterministic overrides for configs. diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index 3729704..1ec58d4 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -92,6 +92,7 @@ global_config() ->      Home = home_dir(),      filename:join([Home, ".config", "rebar3", "rebar.config"]). +-spec global_cache_dir(rebar_dict()) -> file:filename_all().  global_cache_dir(Opts) ->      Home = home_dir(),      rebar_opts:get(Opts, global_rebar_dir, filename:join([Home, ".cache", "rebar3"])). diff --git a/src/rebar_dist_utils.erl b/src/rebar_dist_utils.erl new file mode 100644 index 0000000..f462826 --- /dev/null +++ b/src/rebar_dist_utils.erl @@ -0,0 +1,89 @@ +%%% Common functions to boot/stop distributed setups for +%%% the rebar3 script. +-module(rebar_dist_utils). +-export([either/3, short/2, long/2, find_options/1]). +-include("rebar.hrl"). + +%%%%%%%%%%%%%%%%%% +%%% PUBLIC API %%% +%%%%%%%%%%%%%%%%%% +-spec either(Name::atom(), SName::atom(), Opts::[{setcookie,term()}]) -> atom(). +either(undefined, undefined, _) -> +    'nonode@nohost'; +either(Name, undefined, Opts) -> +    long(Name, Opts), +    node(); +either(undefined, SName, Opts) -> +    short(SName, Opts), +    node(); +either(_, _, _) -> +    ?ABORT("Cannot have both short and long node names defined", []). + +short(Name, Opts) -> +    start(Name, shortnames, Opts). + +long(Name, Opts) -> +    start(Name, longnames, Opts). + +-spec find_options(rebar_state:state()) -> {Long, Short, Opts} when +      Long :: atom(), +      Short :: atom(), +      Opts :: [{setcookie,term()}]. +find_options(State) -> +    {Long, Short} = find_name_options(State), +    case find_cookie_option(State) of +        nocookie -> +            {Long, Short, []}; +        Cookie -> +            {Long, Short, [{setcookie, Cookie}]} +    end. + +%%%%%%%%%%%%%%% +%%% PRIVATE %%% +%%%%%%%%%%%%%%% +start(Name, Type, Opts) -> +    check_epmd(net_kernel:start([Name, Type])), +    setup_cookie(Opts). + +check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) -> +    ?ERROR("Erlang Distribution failed, falling back to nonode@nohost. " +           "Verify that epmd is running and try again.",[]); +check_epmd(_) -> +    ok. + +setup_cookie(Opts) -> +    case {node(), proplists:get_value(setcookie, Opts, nocookie)} of +        {'nonode@nohost', _} -> nocookie; +        {_, nocookie} -> nocookie; +        {Node, Name} -> erlang:set_cookie(Node, Name) +    end. + +find_name_options(State) -> +    {Opts, _} = rebar_state:command_parsed_args(State), +    %% First try the CLI +    case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of +        {undefined, undefined} -> +            %% Else try the config file +            DistOpts = rebar_state:get(State, dist_node, []), +            %% Pick the first one seen to support profile merges +            find_first_name(DistOpts); +        Res -> +            Res +    end. + +find_first_name([]) -> {undefined, undefined}; +find_first_name([{sname,Val}|_]) -> {undefined, Val}; +find_first_name([{name,Val}|_]) -> {Val, undefined}; +find_first_name([_|Opts]) -> find_first_name(Opts). + +find_cookie_option(State) -> +    {Opts, _} = rebar_state:command_parsed_args(State), +    %% First try the CLI +    case proplists:get_value(setcookie, Opts) of +        undefined -> +            %% Else try the config file +            DistOpts = rebar_state:get(State, dist_node, []), +            proplists:get_value(setcookie, DistOpts, nocookie); +        Res -> +            Res +    end. diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 876d047..5a6a5ef 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -201,7 +201,7 @@ parse_tags(Dir) ->          {error, _} ->              {undefined, "0.0.0"};          {ok, Line} -> -            case re:run(Line, "(\\(|\\s)(HEAD,\\s)tag:\\s(v?([^,\\)]+))", [{capture, [3, 4], list}]) of +            case re:run(Line, "(\\(|\\s)(HEAD[^,]*,\\s)tag:\\s(v?([^,\\)]+))", [{capture, [3, 4], list}]) of                  {match,[Tag, Vsn]} ->                      {Tag, Vsn};                  nomatch -> diff --git a/src/rebar_hooks.erl b/src/rebar_hooks.erl index 4e6d486..3af17ca 100644 --- a/src/rebar_hooks.erl +++ b/src/rebar_hooks.erl @@ -2,6 +2,7 @@  -export([run_all_hooks/5          ,run_all_hooks/6 +        ,run_project_and_app_hooks/5          ,format_error/1]).  -include("rebar.hrl"). @@ -20,6 +21,11 @@ run_all_hooks(Dir, Type, Command, Providers, State) ->      run_provider_hooks(Dir, Type, Command, Providers, rebar_state:opts(State), State),      run_hooks(Dir, Type, Command, rebar_state:opts(State), State). +run_project_and_app_hooks(Dir, Type, Command, Providers, State) -> +    ProjectApps = rebar_state:project_apps(State), +    [rebar_hooks:run_all_hooks(Dir, Type, Command, Providers, AppInfo, State) || AppInfo <- ProjectApps], +    run_all_hooks(Dir, Type, Command, Providers, State). +  run_provider_hooks(Dir, Type, Command, Providers, Opts, State) ->      case rebar_opts:get(Opts, provider_hooks, []) of          [] -> @@ -81,6 +87,7 @@ run_hooks(Dir, post, Command, Opts, State) ->  run_hooks(Dir, Type, Command, Opts, State) ->      case rebar_opts:get(Opts, Type, []) of          [] -> +            ?DEBUG("run_hooks(~p, ~p, ~p) -> no hooks defined\n", [Dir, Type, Command]),              ok;          Hooks ->              Env = create_env(State, Opts), diff --git a/src/rebar_log.erl b/src/rebar_log.erl index 06cfa9c..23ae81e 100644 --- a/src/rebar_log.erl +++ b/src/rebar_log.erl @@ -28,20 +28,43 @@  -export([init/2,           set_level/1, +         get_level/0,           error_level/0,           default_level/0, +         intensity/0,           log/3, -         is_verbose/1]). +         is_verbose/1, +         valid_level/1]).  -define(ERROR_LEVEL, 0).  -define(WARN_LEVEL,  1).  -define(INFO_LEVEL,  2).  -define(DEBUG_LEVEL, 3). +-define(DFLT_INTENSITY, high).  %% ===================================================================  %% Public API  %% =================================================================== +%% @doc Returns the color intensity, we first check the application envorinment +%% if that is not set we check the environment variable REBAR_COLOR. +intensity() -> +    case application:get_env(rebar, color_intensity) of +        undefined -> +            R = case os:getenv("REBAR_COLOR") of +                    "high" -> +                        high; +                    "low" -> +                        low; +                    _ -> +                        ?DFLT_INTENSITY +                end, +            application:set_env(rebar, color_intensity, R), +            R; +        {ok, Mode} -> +            Mode +    end. +  init(Caller, Verbosity) ->      Level = case valid_level(Verbosity) of                  ?ERROR_LEVEL -> error; @@ -49,12 +72,25 @@ init(Caller, Verbosity) ->                  ?INFO_LEVEL  -> info;                  ?DEBUG_LEVEL -> debug              end, -    Log = ec_cmd_log:new(Level, Caller), +    Intensity = intensity(), +    Log = ec_cmd_log:new(Level, Caller, Intensity), +    set_level(valid_level(Verbosity)),      application:set_env(rebar, log, Log).  set_level(Level) ->      ok = application:set_env(rebar, log_level, Level). +get_level() -> +    case application:get_env(rebar, log_level) of +        undefined -> +            default_level(); +        {ok, Level} -> +            Level +    end. + +log(Level = error, Str, Args) -> +    {ok, LogState} = application:get_env(rebar, log), +    ec_cmd_log:Level(LogState, lists:flatten(cf:format("~!^~s~n", [Str])), Args);  log(Level, Str, Args) ->      {ok, LogState} = application:get_env(rebar, log),      ec_cmd_log:Level(LogState, Str++"~n", Args). @@ -65,9 +101,9 @@ default_level() -> ?INFO_LEVEL.  is_verbose(State) ->      rebar_state:get(State, is_verbose, false). +valid_level(Level) -> +    erlang:max(?ERROR_LEVEL, erlang:min(Level, ?DEBUG_LEVEL)). +  %% ===================================================================  %% Internal functions  %% =================================================================== - -valid_level(Level) -> -    erlang:max(?ERROR_LEVEL, erlang:min(Level, ?DEBUG_LEVEL)). diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index c56009e..d4b8a14 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -7,7 +7,9 @@          ,registry_dir/1          ,package_dir/1          ,registry_checksum/2 +        ,find_highest_matching/6          ,find_highest_matching/4 +        ,find_all/3          ,verify_table/1          ,format_error/1]). @@ -65,22 +67,28 @@ deps(Name, Vsn, State) ->          deps_(Name, Vsn, State)      catch          _:_ -> -            handle_missing_package(Name, Vsn, State) +            handle_missing_package({Name, Vsn}, State, fun(State1) -> deps_(Name, Vsn, State1) end)      end.  deps_(Name, Vsn, State) ->      ?MODULE:verify_table(State),      ets:lookup_element(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, 2). -handle_missing_package(Name, Vsn, State) -> -    ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]), +handle_missing_package(Dep, State, Fun) -> +    case Dep of +        {Name, Vsn} -> +            ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]); +        _ -> +            ?INFO("Package ~p not found. Fetching registry updates and trying again...", [Dep]) +    end, +      {ok, State1} = rebar_prv_update:do(State),      try -        deps_(Name, Vsn, State1) +        Fun(State1)      catch          _:_ ->              %% Even after an update the package is still missing, time to error out -            throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)})) +            throw(?PRV_ERROR({missing_package, Dep}))      end.  registry_dir(State) -> @@ -139,16 +147,43 @@ registry_checksum({pkg, Name, Vsn}, State) ->  %% `~> 2.0` | `>= 2.0.0 and < 3.0.0`  %% `~> 2.1` | `>= 2.1.0 and < 3.0.0`  find_highest_matching(Dep, Constraint, Table, State) -> +    find_highest_matching(undefined, undefined, Dep, Constraint, Table, State). + +find_highest_matching(Pkg, PkgVsn, Dep, Constraint, Table, State) -> +    try find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) of +        none -> +            handle_missing_package(Dep, State, +                                   fun(State1) -> +                                       find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1) +                                   end); +        Result -> +            Result +    catch +        _:_ -> +            handle_missing_package(Dep, State, +                                   fun(State1) -> +                                       find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1) +                                   end) +    end. + +find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) -> +    try find_all(Dep, Table, State) of +        {ok, [Vsn]} -> +            handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint); +        {ok, [HeadVsn | VsnTail]} -> +                            {ok, handle_vsns(Constraint, HeadVsn, VsnTail)} +    catch +        error:badarg -> +            none +    end. + +find_all(Dep, Table, State) ->      ?MODULE:verify_table(State),      try ets:lookup_element(Table, Dep, 2) of -        [[HeadVsn | VsnTail]] -> -            {ok, handle_vsns(Constraint, HeadVsn, VsnTail)}; -        [[Vsn]] -> -            handle_single_vsn(Dep, Vsn, Constraint); -        [Vsn] -> -            handle_single_vsn(Dep, Vsn, Constraint); -        [HeadVsn | VsnTail] -> -            {ok, handle_vsns(Constraint, HeadVsn, VsnTail)} +        [Vsns] when is_list(Vsns)-> +            {ok, Vsns}; +        Vsns -> +            {ok, Vsns}      catch          error:badarg ->              none @@ -165,18 +200,26 @@ handle_vsns(Constraint, HeadVsn, VsnTail) ->                          end                  end, HeadVsn, VsnTail). -handle_single_vsn(Dep, Vsn, Constraint) -> +handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint) ->      case ec_semver:pes(Vsn, Constraint) of          true ->              {ok, Vsn};          false -> -            ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. " -                 "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]), +            case {Pkg, PkgVsn} of +                {undefined, undefined} -> +                    ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. " +                          "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]); +                _ -> +                    ?WARN("[~s:~s] Only existing version of ~s is ~s which does not match constraint ~~> ~s. " +                          "Using anyway, but it is not guaranteed to work.", [Pkg, PkgVsn, Dep, Vsn, Constraint]) +            end,              {ok, Vsn}      end. -format_error({missing_package, Package, Version}) -> -    io_lib:format("Package not found in registry: ~s-~s.", [Package, Version]). +format_error({missing_package, {Name, Vsn}}) -> +    io_lib:format("Package not found in registry: ~s-~s.", [ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)]); +format_error({missing_package, Dep}) -> +    io_lib:format("Package not found in registry: ~p.", [Dep]).  verify_table(State) ->      ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State). diff --git a/src/rebar_plugins.erl b/src/rebar_plugins.erl index 3c33498..68ba6da 100644 --- a/src/rebar_plugins.erl +++ b/src/rebar_plugins.erl @@ -3,7 +3,8 @@  -module(rebar_plugins). --export([top_level_install/1 +-export([project_plugins_install/1 +        ,top_level_install/1          ,project_apps_install/1          ,install/2          ,handle_plugins/3 @@ -15,6 +16,16 @@  %% Public API  %% =================================================================== +-spec project_plugins_install(rebar_state:t()) -> rebar_state:t(). +project_plugins_install(State) -> +    Profiles = rebar_state:current_profiles(State), +    State1 = rebar_state:allow_provider_overrides(State, true), +    State2 = lists:foldl(fun(Profile, StateAcc) -> +                             Plugins = rebar_state:get(State, {project_plugins, Profile}, []), +                             handle_plugins(Profile, Plugins, StateAcc) +                         end, State1, Profiles), +    rebar_state:allow_provider_overrides(State2, false). +  -spec top_level_install(rebar_state:t()) -> rebar_state:t().  top_level_install(State) ->      Profiles = rebar_state:current_profiles(State), diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 4be50d8..5712fbf 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -37,6 +37,7 @@ init(State) ->  -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.  do(State) -> +    setup_name(State),      Tests = prepare_tests(State),      case compile(State, Tests) of          %% successfully compiled apps @@ -52,14 +53,16 @@ do(State, Tests) ->      %% Run ct provider prehooks      Providers = rebar_state:providers(State),      Cwd = rebar_dir:get_cwd(), -    rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), + +    %% Run ct provider pre hooks for all project apps and top level project hooks +    rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),      case Tests of          {ok, T} ->              case run_tests(State, T) of                  ok    -> -                    %% Run ct provider posthooks -                    rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), +                    %% Run ct provider post hooks for all project apps and top level project hooks +                    rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State),                      rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),                      {ok, State};                  Error -> @@ -103,6 +106,10 @@ format_error({multiple_errors, Errors}) ->  %% Internal functions  %% =================================================================== +setup_name(State) -> +    {Long, Short, Opts} = rebar_dist_utils:find_options(State), +    rebar_dist_utils:either(Long, Short, Opts). +  prepare_tests(State) ->      %% command line test options      CmdOpts = cmdopts(State), @@ -131,6 +138,8 @@ transform_opts([{testcase, Cases}|Rest], Acc) ->      transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]);  transform_opts([{config, Configs}|Rest], Acc) ->      transform_opts(Rest, [{config, split_string(Configs)}|Acc]); +transform_opts([{include, Includes}|Rest], Acc) -> +    transform_opts(Rest, [{include, split_string(Includes)}|Acc]);  transform_opts([{logopts, LogOpts}|Rest], Acc) ->      transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]);  transform_opts([{force_stop, "true"}|Rest], Acc) -> @@ -164,10 +173,15 @@ cfgopts(State) ->      end.  ensure_opts([], Acc) -> lists:reverse(Acc); -ensure_opts([{test_spec, _}|_Rest], _Acc) -> -    ?PRV_ERROR({badconfig, "Test specs not supported"}); -ensure_opts([{auto_compile, _}|_Rest], _Acc) -> -    ?PRV_ERROR({badconfig, "Auto compile not supported"}); +ensure_opts([{test_spec, _}|Rest], Acc) -> +    ?WARN("Test specs not supported. See http://www.rebar3.org/docs/running-tests#common-test", []), +    ensure_opts(Rest, Acc); +ensure_opts([{cover, _}|Rest], Acc) -> +    ?WARN("Cover specs not supported. See http://www.rebar3.org/docs/running-tests#common-test", []), +    ensure_opts(Rest, Acc); +ensure_opts([{auto_compile, _}|Rest], Acc) -> +    ?WARN("Auto compile not supported", []), +    ensure_opts(Rest, Acc);  ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->      ensure_opts(Rest, [{suite, Suite}|Acc]);  ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) -> @@ -180,8 +194,10 @@ ensure_opts([{suite, Suites}|Rest], Acc) ->      ensure_opts(Rest, [NewSuites|Acc]);  ensure_opts([{K, V}|Rest], Acc) ->      ensure_opts(Rest, [{K, V}|Acc]); -ensure_opts([V|_Rest], _Acc) -> -    ?PRV_ERROR({badconfig, {"Member `~p' of option `~p' must be a 2-tuple", {V, ct_opts}}}). +%% pass through other options, in case of things like config terms +%% in `ct_opts` +ensure_opts([V|Rest], Acc) -> +    ensure_opts(Rest, [V|Acc]).  add_hooks(Opts, State) ->      case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of @@ -243,8 +259,8 @@ application_dirs([App|Rest], Acc) ->      end.  compile(State, {ok, _} = Tests) -> -    %% inject `ct_first_files` and `ct_compile_opts` into the applications -    %% to be compiled +    %% inject `ct_first_files`, `ct_compile_opts` and `include` (from `ct_opts` +    %% and command line options) into the applications to be compiled      case inject_ct_state(State, Tests) of          {ok, NewState} -> do_compile(NewState);          Error          -> Error @@ -264,22 +280,22 @@ do_compile(State) ->  inject_ct_state(State, {ok, Tests}) ->      Apps = rebar_state:project_apps(State), -    case inject_ct_state(State, Apps, []) of +    case inject_ct_state(State, Tests, Apps, []) of          {ok, {NewState, ModdedApps}} ->              test_dirs(NewState, ModdedApps, Tests);          {error, _} = Error           -> Error      end;  inject_ct_state(_State, Error) -> Error. -inject_ct_state(State, [App|Rest], Acc) -> -    case inject(rebar_app_info:opts(App), State) of +inject_ct_state(State, Tests, [App|Rest], Acc) -> +    case inject(rebar_app_info:opts(App), State, Tests) of          {error, _} = Error -> Error;          NewOpts            ->              NewApp = rebar_app_info:opts(App, NewOpts), -            inject_ct_state(State, Rest, [NewApp|Acc]) +            inject_ct_state(State, Tests, Rest, [NewApp|Acc])      end; -inject_ct_state(State, [], Acc) -> -    case inject(rebar_state:opts(State), State) of +inject_ct_state(State, Tests, [], Acc) -> +    case inject(rebar_state:opts(State), State, Tests) of          {error, _} = Error -> Error;          NewOpts            ->            {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}} @@ -292,26 +308,55 @@ opts(Opts, Key, Default) ->              ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})      end. -inject(Opts, State) -> erl_opts(Opts, State). +inject(Opts, State, Tests) -> erl_opts(Opts, State, Tests). -erl_opts(Opts, State) -> +erl_opts(Opts, State, Tests) ->      %% append `ct_compile_opts` to app defined `erl_opts`      ErlOpts = opts(Opts, erl_opts, []),      CTOpts = opts(Opts, ct_compile_opts, []),      case add_transforms(append(CTOpts, ErlOpts), State) of -        {error, Error} -> {error, Error}; -        NewErlOpts     -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts)) +        {error, _} = Error -> Error; +        NewErlOpts         -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts), Tests)      end. -first_files(Opts) -> +first_files(Opts, Tests) ->      %% append `ct_first_files` to app defined `erl_first_files`      FirstFiles = opts(Opts, erl_first_files, []),      CTFirstFiles = opts(Opts, ct_first_files, []),      case append(CTFirstFiles, FirstFiles) of          {error, _} = Error -> Error; -        NewFirstFiles  -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles) +        NewFirstFiles      -> include_files(rebar_opts:set(Opts, erl_first_files, NewFirstFiles), Tests) +    end. + +include_files(Opts, Tests) -> +    %% append include dirs from command line and `ct_opts` to app defined +    %% `erl_opts` +    ErlOpts = opts(Opts, erl_opts, []), +    Includes = proplists:get_value(include, Tests, []), +    Is = lists:map(fun(I) -> {i, I} end, Includes), +    case append(Is, ErlOpts) of +        {error, _} = Error -> Error; +        NewIncludes        -> ct_macro(rebar_opts:set(Opts, erl_opts, NewIncludes)) +    end. + +ct_macro(Opts) -> +    ErlOpts = opts(Opts, erl_opts, []), +    NewOpts = safe_define_ct_macro(ErlOpts), +    rebar_opts:set(Opts, erl_opts, NewOpts). + +safe_define_ct_macro(Opts) -> +    %% defining a compile macro twice results in an exception so +    %% make sure 'COMMON_TEST' is only defined once +    case test_defined(Opts) of +       true  -> Opts; +       false -> [{d, 'COMMON_TEST'}|Opts]      end. +test_defined([{d, 'COMMON_TEST'}|_]) -> true; +test_defined([{d, 'COMMON_TEST', true}|_]) -> true; +test_defined([_|Rest]) -> test_defined(Rest); +test_defined([]) -> false. +  append({error, _} = Error, _) -> Error;  append(_, {error, _} = Error) -> Error;  append(A, B) -> A ++ B. @@ -597,8 +642,12 @@ ct_opts(_State) ->       {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer       {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)},       {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, +     {include, undefined, "include", string, help(include)},       {readable, undefined, "readable", boolean, help(readable)}, -     {verbose, $v, "verbose", boolean, help(verbose)} +     {verbose, $v, "verbose", boolean, help(verbose)}, +     {name, undefined, "name", atom, help(name)}, +     {sname, undefined, "sname", atom, help(sname)}, +     {setcookie, undefined, "setcookie", atom, help(setcookie)}      ].  help(dir) -> @@ -647,10 +696,17 @@ help(scale_timetraps) ->      "Scale timetraps";  help(create_priv_dir) ->      "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)"; +help(include) -> +    "Directories containing additional include files";  help(readable) ->      "Shows test case names and only displays logs to shell on failures";  help(verbose) ->      "Verbose output"; +help(name) -> +    "Gives a long name to the node"; +help(sname) -> +    "Gives a short name to the node"; +help(setcookie) -> +    "Sets the cookie if the node is distributed";  help(_) ->      "". - diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 834eb98..622ee60 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -97,7 +97,7 @@ do(State) ->      end.  %% This is used to workaround dialyzer quirk discussed here -%% https://github.com/rebar/rebar3/pull/489#issuecomment-107953541 +%% https://github.com/erlang/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() -> diff --git a/src/rebar_prv_edoc.erl b/src/rebar_prv_edoc.erl index e7048b6..6cefe14 100644 --- a/src/rebar_prv_edoc.erl +++ b/src/rebar_prv_edoc.erl @@ -32,13 +32,19 @@ init(State) ->  do(State) ->      code:add_pathsa(rebar_state:code_paths(State, all_deps)),      ProjectApps = rebar_state:project_apps(State), +    Providers = rebar_state:providers(State),      EDocOpts = rebar_state:get(State, edoc_opts, []), +    Cwd = rebar_state:dir(State), +    rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),      lists:foreach(fun(AppInfo) -> +                          rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, AppInfo, State),                            AppName = ec_cnv:to_list(rebar_app_info:name(AppInfo)),                            ?INFO("Running edoc for ~s", [AppName]),                            AppDir = rebar_app_info:dir(AppInfo), -                          ok = edoc:application(list_to_atom(AppName), AppDir, EDocOpts) +                          ok = edoc:application(list_to_atom(AppName), AppDir, EDocOpts), +                          rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, AppInfo, State)                    end, ProjectApps), +    rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State),      rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),      {ok, State}. diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index a1a4408..942fd10 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -51,19 +51,20 @@ do(State) ->  do(State, Tests) ->      ?INFO("Performing EUnit tests...", []), +    setup_name(State),      rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]),      %% Run eunit provider prehooks      Providers = rebar_state:providers(State),      Cwd = rebar_dir:get_cwd(), -    rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), +    rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),      case validate_tests(State, Tests) of          {ok, T} ->              case run_tests(State, T) of                  {ok, State1} ->                      %% Run eunit provider posthooks -                    rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), +                    rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State1),                      rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),                      {ok, State1};                  Error -> @@ -106,6 +107,10 @@ format_error({error, Error}) ->  %% Internal functions  %% =================================================================== +setup_name(State) -> +    {Long, Short, Opts} = rebar_dist_utils:find_options(State), +    rebar_dist_utils:either(Long, Short, Opts). +  prepare_tests(State) ->      %% parse and translate command line tests      CmdTests = resolve_tests(State), @@ -190,7 +195,7 @@ dedupe_tests({AppMods, TestMods}) ->      %% in AppMods that will trigger it      F = fun(Mod) ->          M = filename:basename(Mod, ".erl"), -        MatchesTest = fun(Dir) -> filename:basename(Dir, ".erl") ++ "_tests" == M end,  +        MatchesTest = fun(Dir) -> filename:basename(Dir, ".erl") ++ "_tests" == M end,          case lists:any(MatchesTest, AppMods) of              false -> {true, {module, list_to_atom(M)}};              true  -> false @@ -244,9 +249,27 @@ first_files(Opts) ->      EUnitFirstFiles = opts(Opts, eunit_first_files, []),      case append(EUnitFirstFiles, FirstFiles) of          {error, _} = Error -> Error; -        NewFirstFiles  -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles) +        NewFirstFiles  -> eunit_macro(rebar_opts:set(Opts, erl_first_files, NewFirstFiles))      end. +eunit_macro(Opts) -> +    ErlOpts = opts(Opts, erl_opts, []), +    NewOpts = safe_define_eunit_macro(ErlOpts), +    rebar_opts:set(Opts, erl_opts, NewOpts). + +safe_define_eunit_macro(Opts) -> +    %% defining a compile macro twice results in an exception so +    %% make sure 'EUNIT' is only defined once +    case test_defined(Opts) of +       true  -> Opts; +       false -> [{d, 'EUNIT'}|Opts] +    end. + +test_defined([{d, 'EUNIT'}|_]) -> true; +test_defined([{d, 'EUNIT', true}|_]) -> true; +test_defined([_|Rest]) -> test_defined(Rest); +test_defined([]) -> false. +  append({error, _} = Error, _) -> Error;  append(_, {error, _} = Error) -> Error;  append(A, B) -> A ++ B. @@ -457,15 +480,21 @@ eunit_opts(_State) ->      [{app, undefined, "app", string, help(app)},       {application, undefined, "application", string, help(app)},       {cover, $c, "cover", boolean, help(cover)}, -     {dir, undefined, "dir", string, help(dir)}, -     {file, undefined, "file", string, help(file)}, -     {module, undefined, "module", string, help(module)}, -     {suite, undefined, "suite", string, help(module)}, -     {verbose, $v, "verbose", boolean, help(verbose)}]. - -help(app)     -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`."; -help(cover)   -> "Generate cover data. Defaults to false."; -help(dir)     -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; -help(file)    -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; -help(module)  -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; -help(verbose) -> "Verbose output. Defaults to false.". +     {dir, $d, "dir", string, help(dir)}, +     {file, $f, "file", string, help(file)}, +     {module, $m, "module", string, help(module)}, +     {suite, $s, "suite", string, help(module)}, +     {verbose, $v, "verbose", boolean, help(verbose)}, +     {name, undefined, "name", atom, help(name)}, +     {sname, undefined, "sname", atom, help(sname)}, +     {setcookie, undefined, "setcookie", atom, help(setcookie)}]. + +help(app)       -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`."; +help(cover)     -> "Generate cover data. Defaults to false."; +help(dir)       -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`."; +help(file)      -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`."; +help(module)    -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`."; +help(verbose)   -> "Verbose output. Defaults to false."; +help(name)      -> "Gives a long name to the node"; +help(sname)     -> "Gives a short name to the node"; +help(setcookie) -> "Sets the cookie if the node is distributed". diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index a484c5f..5e6aa4c 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -35,7 +35,8 @@  -include("rebar.hrl").  -include_lib("providers/include/providers.hrl"). --export([handle_deps_as_profile/4, +-export([do_/1, +         handle_deps_as_profile/4,           profile_dep_dir/2,           find_cycles/1,           cull_compile/2]). @@ -69,8 +70,11 @@ init(State) ->  -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.  do(State) -> +    ?INFO("Verifying dependencies...", []), +    do_(State). + +do_(State) ->      try -        ?INFO("Verifying dependencies...", []),          Profiles = rebar_state:current_profiles(State),          ProjectApps = rebar_state:project_apps(State), diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl index 8578979..cbe8dfe 100644 --- a/src/rebar_prv_lock.erl +++ b/src/rebar_prv_lock.erl @@ -35,8 +35,7 @@ do(State) ->              OldLocks = rebar_state:get(State, {locks, default}, []),              Locks = lists:keysort(1, build_locks(State)),              Dir = rebar_state:dir(State), -            file:write_file(filename:join(Dir, ?LOCK_FILE), -                            io_lib:format("~p.~n", [Locks])), +            rebar_config:write_lock_file(filename:join(Dir, ?LOCK_FILE), Locks),              State1 = rebar_state:set(State, {locks, default}, Locks),              OldLockNames = [element(1,L) || L <- OldLocks], diff --git a/src/rebar_prv_packages.erl b/src/rebar_prv_packages.erl index 998320c..7217ab8 100644 --- a/src/rebar_prv_packages.erl +++ b/src/rebar_prv_packages.erl @@ -28,22 +28,19 @@ init(State) ->  -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.  do(State) ->      rebar_packages:packages(State), -    print_packages(), +    case rebar_state:command_args(State) of +        [Name] -> +            print_packages(get_packages(iolist_to_binary(Name))); +        _ -> +            print_packages(sort_packages()) +    end,      {ok, State}.  -spec format_error(any()) -> iolist().  format_error(load_registry_fail) ->      "Failed to load package regsitry. Try running 'rebar3 update' to fix". -print_packages() -> -    SortedPkgs = ets:foldl(fun({package_index_version, _}, Acc) -> -                                   Acc; -                              ({Pkg, Vsns}, Acc) -> -                                   orddict:store(Pkg, Vsns, Acc); -                              (_, Acc) -> -                                   Acc -                           end, orddict:new(), ?PACKAGE_TABLE), - +print_packages(Pkgs) ->      orddict:map(fun(Name, Vsns) ->                          SortedVsns = lists:sort(fun(A, B) ->                                                          ec_semver:lte(ec_semver:parse(A) @@ -51,7 +48,20 @@ print_packages() ->                                                  end, Vsns),                          VsnStr = join(SortedVsns, <<", ">>),                          ?CONSOLE("~s:~n    Versions: ~s~n", [Name, VsnStr]) -                end, SortedPkgs). +                end, Pkgs). + +sort_packages() -> +    ets:foldl(fun({package_index_version, _}, Acc) -> +                      Acc; +                 ({Pkg, Vsns}, Acc) -> +                      orddict:store(Pkg, Vsns, Acc); +                 (_, Acc) -> +                      Acc +              end, orddict:new(), ?PACKAGE_TABLE). + +get_packages(Name) -> +    ets:lookup(?PACKAGE_TABLE, Name). +  -spec join([binary()], binary()) -> binary().  join([Bin], _Sep) -> diff --git a/src/rebar_prv_path.erl b/src/rebar_prv_path.erl index 37c9834..4e88496 100644 --- a/src/rebar_prv_path.erl +++ b/src/rebar_prv_path.erl @@ -95,10 +95,14 @@ print_paths_if_exist(Paths, State) ->  project_deps(State) ->      Profiles = rebar_state:current_profiles(State), -    List = lists:foldl(fun(Profile, Acc) -> rebar_state:get(State, {deps, Profile}, []) ++ Acc end, [], Profiles), -    Deps = [normalize(Name) || {Name, _} <- List], +    DepList = lists:foldl(fun(Profile, Acc) -> rebar_state:get(State, {deps, Profile}, []) ++ Acc end, [], Profiles), +    LockList = lists:foldl(fun(Profile, Acc) -> rebar_state:get(State, {locks, Profile}, []) ++ Acc end, [], Profiles), +    Deps = [normalize(name(Dep)) || Dep <- DepList++LockList],      lists:usort(Deps). +name(App) when is_tuple(App) -> element(1, App); +name(Name) when is_binary(Name); is_list(Name); is_atom(Name) -> Name. +  normalize(AppName) when is_list(AppName) -> AppName;  normalize(AppName) when is_atom(AppName) -> atom_to_list(AppName);  normalize(AppName) when is_binary(AppName) -> binary_to_list(AppName). diff --git a/src/rebar_prv_plugins_upgrade.erl b/src/rebar_prv_plugins_upgrade.erl index 87f20df..03521c7 100644 --- a/src/rebar_prv_plugins_upgrade.erl +++ b/src/rebar_prv_plugins_upgrade.erl @@ -78,13 +78,14 @@ upgrade(Plugin, State) ->  find_plugin(Plugin, Profiles, State) ->      ec_lists:search(fun(Profile) -> -                            Plugins = rebar_state:get(State, {plugins, Profile}, []), -                            case rebar_utils:tup_find(list_to_atom(Plugin), Plugins) of -                                false -> -                                    not_found; -                                P -> -                                    {ok, P} -                            end +                        Plugins = rebar_state:get(State, {plugins, Profile}, []) ++ +                            rebar_state:get(State, {project_plugins, Profile}, []), +                        case rebar_utils:tup_find(list_to_atom(Plugin), Plugins) of +                            false -> +                                not_found; +                            P -> +                                {ok, P} +                        end                      end, Profiles).  build_plugin(AppInfo, Apps, State) -> diff --git a/src/rebar_prv_report.erl b/src/rebar_prv_report.erl index 587fad7..d6c8b60 100644 --- a/src/rebar_prv_report.erl +++ b/src/rebar_prv_report.erl @@ -13,7 +13,7 @@  -define(PROVIDER, report).  -define(DEPS, []). --define(ISSUES_URL, "https://github.com/rebar/rebar3/issues"). +-define(ISSUES_URL, "https://github.com/erlang/rebar3/issues").  %% ===================================================================  %% Public API diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index ea759fc..0ede495 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -64,6 +64,8 @@ init(State) ->                           "Gives a long name to the node."},                          {sname, undefined, "sname", atom,                           "Gives a short name to the node."}, +                        {setcookie, undefined, "setcookie", atom, +                         "Sets the cookie if the node is distributed."},                          {script_file, undefined, "script", string,                           "Path to an escript file to run before "                           "starting the project apps. Defaults to " @@ -131,11 +133,25 @@ kill_old_user() ->      %% fully die      [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],      user ! {'EXIT', P, normal}, % pretend the port died, then the port can die! +    exit(P, kill), +    wait_for_port_death(1000, P),      OldUser. +wait_for_port_death(N, _) when N < 0 -> +    %% This risks displaying a warning! +    whatever; +wait_for_port_death(N, P) -> +    case erlang:port_info(P) of +        undefined -> +            ok; +        _ -> +            timer:sleep(10), +            wait_for_port_death(N-10, P) +    end. +  setup_new_shell() -> -    %% terminate the current user supervision structure -    ok = supervisor:terminate_child(kernel_sup, user), +    %% terminate the current user supervision structure, if any +    _ = supervisor:terminate_child(kernel_sup, user),      %% start a new shell (this also starts a new user under the correct group)      _ = user_drv:start(),      %% wait until user_drv and user have been registered (max 3 seconds) @@ -176,7 +192,9 @@ rewrite_leaders(OldUser, NewUser) ->          %% 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) +        remove_error_handler(3), +        %% reset the tty handler once more for remote shells +        error_logger:swap_handler(tty)      catch          E:R -> % may fail with custom loggers              ?DEBUG("Logger changes failed for ~p:~p (~p)", [E,R,erlang:get_stacktrace()]), @@ -253,23 +271,8 @@ simulate_proc_lib() ->      put('$initial_call', {rebar_agent, init, 1}).  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} -> -            check_epmd(net_kernel:start([Name, longnames])); -        {undefined, SName} -> -            check_epmd(net_kernel:start([SName, shortnames])); -        {_, _} -> -            ?ABORT("Cannot have both short and long node names defined", []) -    end. - -check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) -> -    ?ERROR("Erlang Distribution failed, falling back to nonode@nohost. " -           "Verify that epmd is running and try again.",[]); -check_epmd(_) -> -    ok. +    {Long, Short, Opts} = rebar_dist_utils:find_options(State), +    rebar_dist_utils:either(Long, Short, Opts).  find_apps_to_boot(State) ->      %% Try the shell_apps option @@ -327,7 +330,8 @@ reread_config(State) ->          ConfigList ->              try                  [application:set_env(Application, Key, Val) -                  || {Application, Items} <- ConfigList, +                  || Config <- ConfigList, +                     {Application, Items} <- Config,                       {Key, Val} <- Items]              catch _:_ ->                  ?ERROR("The configuration file submitted could not be read " @@ -390,7 +394,7 @@ add_test_paths(State) ->      ok.  % First try the --config flag, then try the relx sys_config --spec find_config(rebar_state:t()) -> [tuple()] | no_config. +-spec find_config(rebar_state:t()) -> [[tuple()]] | no_config.  find_config(State) ->      case first_value([fun find_config_option/1,                        fun find_config_rebar/1, @@ -438,11 +442,17 @@ find_config_relx(State) ->      debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value,                      "Found config from relx."). --spec consult_config(rebar_state:t(), string()) -> [tuple()]. +-spec consult_config(rebar_state:t(), string()) -> [[tuple()]].  consult_config(State, Filename) ->      Fullpath = filename:join(rebar_dir:root_dir(State), Filename),      ?DEBUG("Loading configuration from ~p", [Fullpath]), -    case rebar_file_utils:try_consult(Fullpath) of +    Config = case rebar_file_utils:try_consult(Fullpath) of          [T] -> T;          [] -> [] -    end. +    end, +    SubConfigs = [consult_config(State, Entry ++ ".config") || +                  Entry <- Config, is_list(Entry) +                 ], + +    [Config | lists:merge(SubConfigs)]. + diff --git a/src/rebar_prv_state.erl b/src/rebar_prv_state.erl new file mode 100644 index 0000000..4fbcb67 --- /dev/null +++ b/src/rebar_prv_state.erl @@ -0,0 +1,44 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_state). + +-behaviour(provider). + +-export([init/1, +         do/1, +         format_error/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, state). +-define(DEPS, []). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> +    Provider = providers:create( +        [{name, ?PROVIDER}, +         {module, ?MODULE}, +         {bare, false}, +         {deps, ?DEPS}, +         {example, "rebar3 state"}, +         {short_desc, "Print current configuration state"}, +         {desc, "Display rebar configuration for debugging purpose"}, +         {opts, []}]), +    State1 = rebar_state:add_provider(State, Provider), +    {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> +    L = rebar_state:to_list(State), +    ?CONSOLE("State:", []), +    [?CONSOLE("  ~w: ~p", [K, V]) || {K,V} <- L], +    {ok, State}. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> +    io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_unlock.erl b/src/rebar_prv_unlock.erl index b049c92..7ff0d89 100644 --- a/src/rebar_prv_unlock.erl +++ b/src/rebar_prv_unlock.erl @@ -46,15 +46,14 @@ do(State) ->              {ok, State};          {error, Reason} ->              ?PRV_ERROR({file,Reason}); -        {ok, [Locks]} -> +        {ok, _} -> +            Locks = rebar_config:consult_lock_file(LockFile),              case handle_unlocks(State, Locks, LockFile) of                  ok ->                      {ok, State};                  {error, Reason} ->                      ?PRV_ERROR({file,Reason}) -            end; -        {ok, _Other} -> -            ?PRV_ERROR(unknown_lock_format) +            end      end.  -spec format_error(any()) -> iolist(). @@ -74,7 +73,7 @@ handle_unlocks(State, Locks, LockFile) ->          _ when Names =:= [] -> % implicitly all locks              file:delete(LockFile);          NewLocks -> -            file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])) +            rebar_config:write_lock_file(LockFile, NewLocks)      end.  parse_names(Bin) -> diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index 0e3b9a0..5e1e253 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -11,6 +11,10 @@  -export([hex_to_index/1]). +-ifdef(TEST). +-export([cmp_/6, cmpl_/6, valid_vsn/1]). +-endif. +  -include("rebar.hrl").  -include_lib("providers/include/providers.hrl"). @@ -99,7 +103,7 @@ hex_to_index(State) ->                  ets:foldl(fun({{Pkg, PkgVsn}, [Deps, Checksum, BuildTools | _]}, _) when is_list(BuildTools) ->                                    case lists:any(fun is_supported/1, BuildTools) of                                        true -> -                                          DepsList = update_deps_list(Deps, Registry, State), +                                          DepsList = update_deps_list(Pkg, PkgVsn, Deps, Registry, State),                                            ets:insert(?PACKAGE_TABLE, {{Pkg, PkgVsn}, DepsList, Checksum});                                        false ->                                            true @@ -137,20 +141,114 @@ hex_to_index(State) ->              fail      end. -update_deps_list(Deps, HexRegistry, State) -> +update_deps_list(Pkg, PkgVsn, Deps, HexRegistry, State) ->      lists:foldl(fun([Dep, DepVsn, false, _AppName | _], DepsListAcc) -> -                        case DepVsn of -                            <<"~> ", Vsn/binary>> -> -                                case rebar_packages:find_highest_matching(Dep, Vsn, HexRegistry, State) of -                                    {ok, HighestDepVsn} -> -                                        [{Dep, HighestDepVsn} | DepsListAcc]; -                                    none -> -                                        ?WARN("Missing registry entry for package ~s. Try to fix with `rebar3 update`", [Dep]), -                                        DepsListAcc -                                end; -                            Vsn -> +                        Dep1 = {Pkg, PkgVsn, Dep}, +                        case {valid_vsn(DepVsn), DepVsn} of +                            %% Those are all not perfectly implemented! +                            %% and doubled since spaces seem not to be +                            %% enforced +                            {false, Vsn} -> +                                ?WARN("[~s:~s], Bad dependency version for ~s: ~s.", +                                      [Pkg, PkgVsn, Dep, Vsn]), +                                DepsListAcc; +                            {_, <<"~>", Vsn/binary>>} -> +                                highest_matching(Dep1, rm_ws(Vsn), HexRegistry, +                                                 State, DepsListAcc); +                            {_, <<">=", Vsn/binary>>} -> +                                cmp(Dep1, rm_ws(Vsn), HexRegistry, State, +                                    DepsListAcc, fun ec_semver:gte/2); +                            {_, <<">", Vsn/binary>>} -> +                                cmp(Dep1, rm_ws(Vsn), HexRegistry, State, +                                    DepsListAcc, fun ec_semver:gt/2); +                            {_, <<"<=", Vsn/binary>>} -> +                                cmpl(Dep1, rm_ws(Vsn), HexRegistry, State, +                                     DepsListAcc, fun ec_semver:lte/2); +                            {_, <<"<", Vsn/binary>>} -> +                                cmpl(Dep1, rm_ws(Vsn), HexRegistry, State, +                                     DepsListAcc, fun ec_semver:lt/2); +                            {_, <<"==", Vsn/binary>>} -> +                                [{Dep, Vsn} | DepsListAcc]; +                            {_, Vsn} ->                                  [{Dep, Vsn} | DepsListAcc]                          end;                     ([_Dep, _DepVsn, true, _AppName | _], DepsListAcc) ->                          DepsListAcc                  end, [], Deps). + +rm_ws(<<" ", R/binary>>) -> +    rm_ws(R); +rm_ws(R) -> +    R. + +valid_vsn(Vsn) -> +    %% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js +    SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?" +        "(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?", +    SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$", +    re:run(Vsn, SupportedVersions) =/= nomatch. + +highest_matching({Pkg, PkgVsn, Dep}, Vsn, HexRegistry, State, DepsListAcc) -> +    case rebar_packages:find_highest_matching(Pkg, PkgVsn, Dep, Vsn, HexRegistry, State) of +        {ok, HighestDepVsn} -> +            [{Dep, HighestDepVsn} | DepsListAcc]; +        none -> +            ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`", +                  [Pkg, PkgVsn, Dep]), +            DepsListAcc +    end. + +cmp({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) -> +    {ok, Vsns}  = rebar_packages:find_all(Dep, HexRegistry, State), +    cmp_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun). + + +cmp_(undefined, _MinVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) -> +    ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`", +          [Pkg, PkgVsn, Dep]), +    DepsListAcc; +cmp_(HighestDepVsn, _MinVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) -> +    [{Dep, HighestDepVsn} | DepsListAcc]; + +cmp_(BestMatch, MinVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) -> +    case CmpFun(Vsn, MinVsn) of +        true -> +            cmp_(Vsn, Vsn, R, DepsListAcc, Dep, CmpFun); +        false  -> +            cmp_(BestMatch, MinVsn, R, DepsListAcc, Dep, CmpFun) +    end. + +%% We need to treat this differently since we want a version that is LOWER but +%% the higest possible one. +cmpl({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) -> +    {ok, Vsns}  = rebar_packages:find_all(Dep, HexRegistry, State), +    cmpl_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun). + +cmpl_(undefined, _MaxVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) -> +    ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`", +          [Pkg, PkgVsn, Dep]), +    DepsListAcc; + +cmpl_(HighestDepVsn, _MaxVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) -> +    [{Dep, HighestDepVsn} | DepsListAcc]; + +cmpl_(undefined, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) -> +    case CmpFun(Vsn, MaxVsn) of +        true -> +            cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun); +        false  -> +            cmpl_(undefined, MaxVsn, R, DepsListAcc, Dep, CmpFun) +    end; + +cmpl_(BestMatch, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) -> +    case CmpFun(Vsn, MaxVsn) of +        true -> +            case ec_semver:gte(Vsn, BestMatch) of +                true -> +                    cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun); +                false -> +                    cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun) +            end; +        false  -> +            cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun) +    end. diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index a2864ab..c5c43e4 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -61,7 +61,7 @@ do(State) ->              State4 = rebar_state:set(State3, upgrade, true),              UpdatedLocks = [L || L <- rebar_state:lock(State4),                                   lists:keymember(rebar_app_info:name(L), 1, Locks0)], -            Res = rebar_prv_install_deps:do(rebar_state:lock(State4, UpdatedLocks)), +            Res = rebar_prv_install_deps:do_(rebar_state:lock(State4, UpdatedLocks)),              case Res of                  {ok, State5} ->                      rebar_utils:info_useless( diff --git a/src/rebar_relx.erl b/src/rebar_relx.erl index 5d29258..5c653a3 100644 --- a/src/rebar_relx.erl +++ b/src/rebar_relx.erl @@ -14,6 +14,9 @@  -spec do(atom(), string(), atom(), rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.  do(Module, Command, Provider, State) -> +    %% We set the color mode for relx as a application env +    application:set_env(relx, color_intensity, rebar_log:intensity()), +    LogLevel = rebar_log:get_level(),      Options = rebar_state:command_args(State),      DepsDir = rebar_dir:deps_dir(State),      ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), @@ -23,19 +26,21 @@ do(Module, Command, Provider, State) ->      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), +    rebar_hooks:run_project_and_app_hooks(Cwd, pre, Provider, Providers, State),      try          case rebar_state:get(State, relx, []) of              [] ->                  relx:main([{lib_dirs, LibDirs} -                          ,{caller, api} | output_dir(OutputDir, Options)], AllOptions); +                          ,{caller, api} +                          ,{log_level, LogLevel} | output_dir(OutputDir, Options)], AllOptions);              Config ->                  Config1 = merge_overlays(Config),                  relx:main([{lib_dirs, LibDirs}                            ,{config, Config1} -                          ,{caller, api} | output_dir(OutputDir, Options)], AllOptions) +                          ,{caller, api} +                          ,{log_level, LogLevel} | output_dir(OutputDir, Options)], AllOptions)          end, -        rebar_hooks:run_all_hooks(Cwd, post, Provider, Providers, State), +        rebar_hooks:run_project_and_app_hooks(Cwd, post, Provider, Providers, State),          {ok, State}      catch          throw:T -> diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 0c07b2a..a613a00 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -36,9 +36,12 @@           deps_names/1, +         to_list/1,           resources/1, resources/2, add_resource/2, -         providers/1, providers/2, add_provider/2]). +         providers/1, providers/2, add_provider/2, +         allow_provider_overrides/1, allow_provider_overrides/2 +        ]).  -include("rebar.hrl").  -include_lib("providers/include/providers.hrl"). @@ -63,7 +66,8 @@                    all_deps            = []          :: [rebar_app_info:t()],                    resources           = [], -                  providers           = []}). +                  providers           = [], +                  allow_provider_overrides = false  :: boolean()}).  -export_type([t/0]). @@ -103,7 +107,8 @@ new(ParentState, Config, Dir) ->  new(ParentState, Config, Deps, Dir) ->      Opts = ParentState#state_t.opts,      Plugins = proplists:get_value(plugins, Config, []), -    Terms = Deps++[{{plugins, default}, Plugins} | Config], +    ProjectPlugins = proplists:get_value(project_plugins, Config, []), +    Terms = Deps++[{{project_plugins, default}, ProjectPlugins}, {{plugins, default}, Plugins} | Config],      true = rebar_config:verify_config_format(Terms),      LocalOpts = dict:from_list(Terms), @@ -115,13 +120,13 @@ new(ParentState, Config, Deps, Dir) ->  deps_from_config(Dir, Config) ->      case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of -        [D] -> +        [] -> +            [{{deps, default}, proplists:get_value(deps, Config, [])}]; +        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], -            [{{locks, default}, D}, {{deps, default}, Deps}]; -        _ -> -            [{{deps, default}, proplists:get_value(deps, Config, [])}] +            [{{locks, default}, D}, {{deps, default}, Deps}]      end.  base_state() -> @@ -136,7 +141,8 @@ base_state() ->  base_opts(Config) ->      Deps = proplists:get_value(deps, Config, []),      Plugins = proplists:get_value(plugins, Config, []), -    Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins} | Config], +    ProjectPlugins = proplists:get_value(project_plugins, Config, []), +    Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins}, {{project_plugins, default}, ProjectPlugins} | Config],      true = rebar_config:verify_config_format(Terms),      dict:from_list(Terms). @@ -368,8 +374,16 @@ providers(#state_t{providers=Providers}) ->  providers(State, NewProviders) ->      State#state_t{providers=NewProviders}. +allow_provider_overrides(#state_t{allow_provider_overrides=Allow}) -> +    Allow. + +allow_provider_overrides(State, Allow) -> +    State#state_t{allow_provider_overrides=Allow}. +  -spec add_provider(t(), providers:t()) -> t(). -add_provider(State=#state_t{providers=Providers}, Provider) -> +add_provider(State=#state_t{providers=Providers, allow_provider_overrides=true}, Provider) -> +    State#state_t{providers=[Provider | Providers]}; +add_provider(State=#state_t{providers=Providers, allow_provider_overrides=false}, Provider) ->      Name = providers:impl(Provider),      Namespace = providers:namespace(Provider),      Module = providers:module(Provider), @@ -406,6 +420,21 @@ create_logic_providers(ProviderModules, State0) ->              throw({error, "Failed creating providers. Run with DEBUG=1 for stacktrace."})      end. +to_list(#state_t{} = State) -> +    Fields = record_info(fields, state_t), +    Values = tl(tuple_to_list(State)), +    DictSz = tuple_size(dict:new()), +    lists:zip(Fields, [reformat(I, DictSz) || I <- Values]). + +reformat({K,V}, DSz) when is_list(V) -> +    {K, [reformat(I, DSz) || I <- V]}; +reformat(V, DSz) when is_tuple(V), element(1,V) =:= dict, tuple_size(V) =:= DSz -> +    [reformat(I, DSz) || I <- dict:to_list(V)]; +reformat({K,V}, DSz) when is_tuple(V), element(1,V) =:= dict, tuple_size(V) =:= DSz -> +    {K, [reformat(I, DSz) || I <- dict:to_list(V)]}; +reformat(Other, _DSz) -> +    Other. +  %% ===================================================================  %% Internal functions  %% =================================================================== | 
