diff options
53 files changed, 1279 insertions, 275 deletions
diff --git a/.travis.yml b/.travis.yml index 630beb2..ab2d28d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ cache: before_deploy: "rm -rf !(rebar3)" deploy: on: - branch: master + tags: true condition: $TRAVIS_OTP_RELEASE = R16B03-1 provider: s3 access_key_id: AKIAJAPYAQEFYCYSNL7Q diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f175cc2..8077813 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ All files without specific headers can safely be assumed to be under Apache ## Submitting a Bug -Bugs can be submitted to the [Github issue page](https://github.com/rebar/rebar3/issues). +Bugs can be submitted to the [Github issue page](https://github.com/erlang/rebar3/issues). Rebar3 is not perfect software and will be buggy. When submitting a bug, be careful to know the following: @@ -58,7 +58,7 @@ Before requesting or implementing a new feature, please do the following: - Take a look at our [list of plugins](http://www.rebar3.org/docs/using-available-plugins) to know if the feature isn't already supported by the community. -- Verify in existing [tickets](https://github.com/rebar/rebar3/issues) whether +- Verify in existing [tickets](https://github.com/erlang/rebar3/issues) whether the feature might already is in the works, has been moved to a plugin, or has already been rejected. @@ -1,6 +1,6 @@ # Rebar3 -[![Build Status](https://travis-ci.org/rebar/rebar3.svg?branch=master)](https://travis-ci.org/rebar/rebar3) [![Windows build status](https://ci.appveyor.com/api/projects/status/yx4oitd9pvd2kab3?svg=true)](https://ci.appveyor.com/project/TristanSloughter/rebar3) +[![Build Status](https://travis-ci.org/erlang/rebar3.svg?branch=master)](https://travis-ci.org/erlang/rebar3) [![Windows build status](https://ci.appveyor.com/api/projects/status/yx4oitd9pvd2kab3?svg=true)](https://ci.appveyor.com/project/TristanSloughter/rebar3) 1. [What is Rebar3?](#what-is-rebar3) 2. [Why Rebar3?](#why-rebar3) @@ -64,12 +64,12 @@ $ wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3 From Source (assuming you have a full Erlang install): ```bash -$ git clone https://github.com/rebar/rebar3.git +$ git clone https://github.com/erlang/rebar3.git $ cd rebar3 $ ./bootstrap ``` -Stable versions can be obtained from the [releases page](https://github.com/rebar/rebar3/releases). +Stable versions can be obtained from the [releases page](https://github.com/erlang/rebar3/releases). The rebar3 escript can also extract itself with a run script under the user's home directory: @@ -159,13 +159,14 @@ quick feedback, you can try the #rebar channel on asking about things with well known answers. For bug reports, roadmaps, and issues, visit the [github issues -page](https://github.com/rebar/rebar3/issues). +page](https://github.com/erlang/rebar3/issues). General rebar community resources and links: - [Rebar Mailing List](http://lists.basho.com/pipermail/rebar_lists.basho.com/) - #rebar on [irc.freenode.net](http://freenode.net/) -- [issues](https://github.com/rebar/rebar3/issues) +- [issues](https://github.com/erlang/rebar3/issues) - [Documentation](http://www.rebar3.org/v3.0/docs) To contribute to rebar3, please refer to [CONTRIBUTING](CONTRIBUTING.md). + @@ -135,4 +135,5 @@ Pierre Fenoll David Kubecka Stefan Grundmann Carlos Eduardo de Paula -Derek Brown
\ No newline at end of file +Derek Brown +Heinz N. Gies @@ -24,7 +24,7 @@ main(_) -> bootstrap_rebar3(), %% Build rebar.app from rebar.app.src - {ok, App} = rebar_app_info:new(rebar, "3.0.0", filename:absname("_build/default/lib/rebar/")), + {ok, App} = rebar_app_info:new(rebar, "3.1.0", filename:absname("_build/default/lib/rebar/")), rebar_otp_app:compile(rebar_state:new(), App), %% Because we are compiling files that are loaded already we want to silence @@ -92,7 +92,7 @@ fetch({pkg, Name, Vsn}, App) -> Dir = filename:join([filename:absname("_build/default/lib/"), App]), case filelib:is_dir(Dir) of false -> - CDN = "https://s3.amazonaws.com/s3.hex.pm/tarballs", + CDN = "https://repo.hex.pm/tarballs", Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>), Url = string:join([CDN, Package], "/"), case request(Url) of diff --git a/priv/shell-completion/bash/rebar3 b/priv/shell-completion/bash/rebar3 index 3cd3cd7..be9af44 100644 --- a/priv/shell-completion/bash/rebar3 +++ b/priv/shell-completion/bash/rebar3 @@ -77,6 +77,7 @@ _rebar3() --multiply_timetraps \ --scale_timetraps \ --create_priv_dir \ + --include \ --verbose \ --auto_compile \ " @@ -92,7 +93,7 @@ _rebar3() elif [[ ${prev} == escriptize ]] ; then : elif [[ ${prev} == eunit ]] ; then - sopts="-c -e -v" + sopts="-c -e -v -d -f -m -s" lopts="--app --application --cover --dir --error_on_warning --file --module --suite --verbose" elif [[ ${prev} == help ]] ; then : diff --git a/priv/shell-completion/fish/rebar3.fish b/priv/shell-completion/fish/rebar3.fish index 7b63e20..fd28c97 100644 --- a/priv/shell-completion/fish/rebar3.fish +++ b/priv/shell-completion/fish/rebar3.fish @@ -110,6 +110,7 @@ complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l abort_if_missing_ complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l multiply_timetraps -d "Multiply timetraps." complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l scale_timetraps -d "Scale timetraps." complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l create_priv_dir -d "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)." +complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l include -d "Directories containing additional include files." complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -s v -l verbose -d "Enable verbose output. Default: false." complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l auto_compile -d "Let common test compile test suites instead of rebar3." @@ -130,11 +131,11 @@ complete -f -c 'rebar3' -n '__fish_rebar3_needs_command' -a eunit -d "Run EUnit complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l app -d "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l application -d "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s c -l cover -d "Generate cover data" -complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l dir -d "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`" +complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s d -l dir -d "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunut' -s e -l error_on_warning -d "Error on invalid test specifications instead of warning" -complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l file -d "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`" -complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l module -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" -complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l suite -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" +complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s f -l file -d "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`" +complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s m -l module -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" +complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s s -l suite -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s v -l verbose -d "Verbose output" complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l suite -d "Lists of test suites to run" diff --git a/priv/shell-completion/zsh/_rebar3 b/priv/shell-completion/zsh/_rebar3 index f0fb351..0abc340 100644 --- a/priv/shell-completion/zsh/_rebar3 +++ b/priv/shell-completion/zsh/_rebar3 @@ -62,6 +62,7 @@ _rebar3 () { '(--multiply_timetraps)--multiply_timetraps' \ '(--scale_timetraps)--scale_timetraps' \ '(--create_priv_dir)--create_priv_dir' \ + '(--include)--include[Directories containing additional include files]:includes' \ '(-v --verbose)'{-v,--verbose}'[Print coverage analysis]' \ '(--auto_compile)--auto_compile' \ && ret=0 @@ -89,11 +90,11 @@ _rebar3 () { '(--app)--app[Comma separated list of application test suites to run]:suites' \ '(--application)--application[Comma separated list of application test suites to run]:applications' \ '(-c --cover)'{-c,--cover}'[Generate cover data]' \ - '(--dir)--dir[Comma separated list of dirs to load tests from]:dirs' \ + '(-d --dir)'{-d,--dir}'[Comma separated list of dirs to load tests from]:dirs' \ '(-e --error_on_warning)'{-e,--error_on_warning}'[Error on invalid test specifications instead of warning]' \ - '(--file)--file[Comma separated list of files to load tests from]:files' \ - '(--module)--module[Comma separated list of modules to load tests from]:modules' \ - '(--suite)--suite[Comma separated list of modules to load tests from]:modules' \ + '(-f --file)'{-f,--file}'[Comma separated list of files to load tests from]:files' \ + '(-m --module)'{-m,--module}'[Comma separated list of modules to load tests from]:modules' \ + '(-s --suite)'{-s,--suite}'[Comma separated list of modules to load tests from]:modules' \ '(-v --verbose)'{-v,--verbose}'[Verbose output]' \ && ret=0 ;; diff --git a/priv/templates/app.erl b/priv/templates/app.erl index 83eb9a3..62d2ddf 100644 --- a/priv/templates/app.erl +++ b/priv/templates/app.erl @@ -8,8 +8,7 @@ -behaviour(application). %% Application callbacks --export([start/2 - ,stop/1]). +-export([start/2, stop/1]). %%==================================================================== %% API diff --git a/rebar.config b/rebar.config index 52c2f80..5d3b310 100644 --- a/rebar.config +++ b/rebar.config @@ -1,15 +1,15 @@ %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et -{deps, [{erlware_commons, "0.18.0"}, +{deps, [{erlware_commons, "0.20.0"}, {ssl_verify_hostname, "1.0.5"}, - {certifi, "0.3.0"}, + {certifi, "0.4.0"}, {providers, "1.6.0"}, {getopt, "0.8.2"}, {bbmustache, "1.0.4"}, - {relx, "3.15.0"}, + {relx, "3.19.0"}, {cf, "0.2.1"}, - {cth_readable, "1.2.0"}, + {cth_readable, "1.2.2"}, {eunit_formatters, "0.3.1"}]}. {escript_name, rebar3}. @@ -35,32 +35,33 @@ {bootstrap, []}, - {dialyze, [{erl_opts, [debug_info]}]} + {dialyze, [{overrides, [{add, erlware_commons, [{erl_opts, [debug_info]}]}, + {add, ssl_verify_hostname, [{erl_opts, [debug_info]}]}, + {add, certifi, [{erl_opts, [debug_info]}]}, + {add, providers, [{erl_opts, [debug_info]}]}, + {add, getopt, [{erl_opts, [debug_info]}]}, + {add, bbmustache, [{erl_opts, [debug_info]}]}, + {add, relx, [{erl_opts, [debug_info]}]}, + {add, cf, [{erl_opts, [debug_info]}]}, + {add, cth_readable, [{erl_opts, [debug_info]}]}, + {add, eunit_formatters, [{erl_opts, [debug_info]}]}]}, + {erl_opts, [debug_info]}]} ]}. %% Overrides -{overrides, [{override, erlware_commons, [ - {erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, - {platform_define, "^R1[4|5]", deprecated_crypto}, - no_debug_info, - warnings_as_errors]}, - {deps, []}, {plugins, []}, - {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]} - ]}, - {override, bbmustache, [ - {erl_opts, [no_debug_info, - {platform_define, "^[0-9]+", namespaced_types}]}, - {deps, []}, {plugins, []}, - {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]} - ]}, - {override, getopt, [{erl_opts, [no_debug_info]}, - {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]}]}, - {override, providers, [{erl_opts, [no_debug_info]}, - {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]}]}, - {override, relx, [{erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, - {platform_define, "^R1[4|5]", deprecated_crypto}, - no_debug_info, - warnings_as_errors]}, - {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]} - ]} - ]}. +{overrides, [{override, erlware_commons, [{erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, + {platform_define, "^R1[4|5]", deprecated_crypto}, + no_debug_info, + warnings_as_errors]}, + {deps, []}, {plugins, []}]}, + {add, ssl_verify_hostname, [{erl_opts, [no_debug_info]}]}, + {add, certifi, [{erl_opts, [no_debug_info]}]}, + {add, cf, [{erl_opts, [no_debug_info]}]}, + {add, cth_readable, [{erl_opts, [no_debug_info]}]}, + {add, eunit_formatters, [{erl_opts, [no_debug_info]}]}, + {override, bbmustache, [{erl_opts, [no_debug_info, + {platform_define, "^[0-9]+", namespaced_types}]}, + {deps, []}, {plugins, []}]}, + {add, getopt, [{erl_opts, [no_debug_info]}]}, + {add, providers, [{erl_opts, [no_debug_info]}]}, + {add, relx, [{erl_opts, [no_debug_info]}]}]}. diff --git a/rebar.config.sample b/rebar.config.sample index de70998..f57f8dc 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -211,7 +211,7 @@ %% apps to auto-boot with `rebar3 shell'; defaults to apps %% specified in a `relx' tuple, if any. -{shell_apps, [app1, app2]} +{shell, [{apps, [app1, app2]}]}. %% == xref == @@ -1,10 +1,10 @@ [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0}, - {<<"certifi">>,{pkg,<<"certifi">>,<<"0.3.0">>},0}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},0}, {<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0}, - {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.0">>},0}, - {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.18.0">>},0}, + {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.2">>},0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.20.0">>},0}, {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}, - {<<"relx">>,{pkg,<<"relx">>,<<"3.15.0">>},0}, + {<<"relx">>,{pkg,<<"relx">>,<<"3.19.0">>},0}, {<<"ssl_verify_hostname">>,{pkg,<<"ssl_verify_hostname">>,<<"1.0.5">>},0}]. 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 %% =================================================================== diff --git a/test/mock_git_resource.erl b/test/mock_git_resource.erl index 0f4aff6..e922af3 100644 --- a/test/mock_git_resource.erl +++ b/test/mock_git_resource.erl @@ -1,6 +1,6 @@ %%% Mock a git resource and create an app magically for each URL submitted. -module(mock_git_resource). --export([mock/0, mock/1, unmock/0]). +-export([mock/0, mock/1, mock/2, unmock/0]). -define(MOD, rebar_git_resource). %%%%%%%%%%%%%%%%% @@ -24,11 +24,14 @@ mock() -> mock([]). | {pkg, App, term()}, Vsn :: string(). mock(Opts) -> + mock(Opts, create_app). + +mock(Opts, CreateType) -> meck:new(?MOD, [no_link]), mock_lock(Opts), mock_update(Opts), mock_vsn(Opts), - mock_download(Opts), + mock_download(Opts, CreateType), ok. unmock() -> @@ -98,7 +101,7 @@ mock_vsn(Opts) -> %% `{deps, [{"app1", [{app2, ".*", {git, ...}}]}]}' -- basically %% the `deps' option takes a key/value list of terms to output directly %% into a `rebar.config' file to describe dependencies. -mock_download(Opts) -> +mock_download(Opts, CreateType) -> Deps = proplists:get_value(deps, Opts, []), Config = proplists:get_value(config, Opts, []), Default = proplists:get_value(default_vsn, Opts, "0.0.0"), @@ -110,7 +113,7 @@ mock_download(Opts) -> {git, Url, {_, Vsn}} = normalize_git(Git, Overrides, Default), App = app(Url), AppDeps = proplists:get_value({App,Vsn}, Deps, []), - rebar_test_utils:create_app( + rebar_test_utils:CreateType( Dir, App, Vsn, [kernel, stdlib] ++ [element(1,D) || D <- AppDeps] ), diff --git a/test/rebar_ct_SUITE.erl b/test/rebar_ct_SUITE.erl index 94ab690..1da7571 100644 --- a/test/rebar_ct_SUITE.erl +++ b/test/rebar_ct_SUITE.erl @@ -6,8 +6,10 @@ end_per_group/2]). -export([basic_app_default_dirs/1, basic_app_default_beams/1, + basic_app_ct_macro/1, multi_app_default_dirs/1, multi_app_default_beams/1, + multi_app_ct_macro/1, single_app_dir/1, single_extra_dir/1, single_unmanaged_dir/1, @@ -38,9 +40,11 @@ cmd_multiply_timetraps/1, cmd_scale_timetraps/1, cmd_create_priv_dir/1, + cmd_include_dir/1, cfg_opts/1, cfg_arbitrary_opts/1, cfg_test_spec/1, + cfg_cover_spec/1, cfg_atom_suites/1, cover_compiled/1, misspecified_ct_opts/1, @@ -56,16 +60,18 @@ all() -> [{group, basic_app}, {group, ct_opts}, {group, cover}, cfg_opts, cfg_arbitrary_opts, - cfg_test_spec, + cfg_test_spec, cfg_cover_spec, cfg_atom_suites, misspecified_ct_opts, misspecified_ct_compile_opts, misspecified_ct_first_files]. groups() -> [{basic_app, [], [basic_app_default_dirs, - basic_app_default_beams]}, + basic_app_default_beams, + basic_app_ct_macro]}, {multi_app, [], [multi_app_default_dirs, - multi_app_default_beams]}, + multi_app_default_beams, + multi_app_ct_macro]}, {dirs_and_suites, [], [single_app_dir, single_extra_dir, single_unmanaged_dir, @@ -95,7 +101,8 @@ groups() -> [{basic_app, [], [basic_app_default_dirs, cmd_abort_if_missing_suites, cmd_multiply_timetraps, cmd_scale_timetraps, - cmd_create_priv_dir]}, + cmd_create_priv_dir, + cmd_include_dir]}, {cover, [], [cover_compiled]}]. init_per_group(basic_app, Config) -> @@ -118,7 +125,7 @@ init_per_group(basic_app, Config) -> {ok, T} = Tests, Opts = rebar_prv_common_test:translate_paths(NewState, T), - [{result, Opts}, {appnames, [Name]}|C]; + [{result, Opts}, {appnames, [Name]}, {compile_state, NewState}|C]; init_per_group(multi_app, Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_"), @@ -153,7 +160,7 @@ init_per_group(multi_app, Config) -> {ok, T} = Tests, Opts = rebar_prv_common_test:translate_paths(NewState, T), - [{result, Opts}, {appnames, [Name1, Name2]}|C]; + [{result, Opts}, {appnames, [Name1, Name2]}, {compile_state, NewState}|C]; init_per_group(dirs_and_suites, Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_"), @@ -211,7 +218,7 @@ init_per_group(ct_opts, Config) -> {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return), - [{result, State}|C]; + [{result, State}, {name, Name}|C]; init_per_group(cover, Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_opts"), @@ -252,6 +259,15 @@ basic_app_default_beams(Config) -> true = filelib:is_file(File). +basic_app_ct_macro(Config) -> + State = ?config(compile_state, Config), + + [App] = rebar_state:project_apps(State), + Opts = rebar_app_info:opts(App), + ErlOpts = dict:fetch(erl_opts, Opts), + true = lists:member({d, 'COMMON_TEST'}, ErlOpts). + + multi_app_default_dirs(Config) -> AppDir = ?config(apps, Config), [Name1, Name2] = ?config(appnames, Config), @@ -293,6 +309,16 @@ multi_app_default_beams(Config) -> true = filelib:is_file(File2), true = filelib:is_file(File3). +multi_app_ct_macro(Config) -> + State = ?config(compile_state, Config), + + Apps = rebar_state:project_apps(State), + lists:foreach(fun(App) -> + Opts = rebar_app_info:opts(App), + ErlOpts = dict:fetch(erl_opts, Opts), + true = lists:member({d, 'COMMON_TEST'}, ErlOpts) + end, Apps). + single_app_dir(Config) -> AppDir = ?config(apps, Config), [Name1, _Name2] = ?config(appnames, Config), @@ -700,7 +726,6 @@ suite_at_app_root(Config) -> data_dir_correct(Config) -> DataDir = ?config(data_dir, Config), Parts = filename:split(DataDir), - ct:pal(Parts), ["rebar_ct_SUITE_data","test","rebar","lib","test","_build"|_] = lists:reverse(Parts). cmd_label(Config) -> @@ -973,6 +998,29 @@ cmd_create_priv_dir(Config) -> true = lists:member({create_priv_dir, manual_per_tc}, TestOpts). +cmd_include_dir(Config) -> + State = ?config(result, Config), + AppDir = ?config(apps, Config), + + Providers = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--include=foo/bar/baz,qux"]), + + NewState = rebar_state:command_parsed_args(State, GetOptResult), + + Tests = rebar_prv_common_test:prepare_tests(NewState), + {ok, _} = rebar_prv_common_test:compile(NewState, Tests), + + Name = ?config(name, Config), + Beam = filename:join([AppDir, "_build", "test", "lib", Name, "ebin", Name ++ ".beam"]), + + {ok, {_, [{compile_info, Info}]}} = beam_lib:chunks(Beam, [compile_info]), + CompileOpts = proplists:get_value(options, Info), + true = lists:member({i, "foo/bar/baz"}, CompileOpts), + true = lists:member({i, "qux"}, CompileOpts). + cfg_opts(Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_opts_"), @@ -1020,13 +1068,30 @@ cfg_test_spec(Config) -> Vsn = rebar_test_utils:create_random_vsn(), rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), - RebarConfig = [{ct_opts, [{test_spec, "spec/foo.spec"}]}], + RebarConfig = [{ct_opts, [Opt = {test_spec, "spec/foo.spec"}]}], {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return), - {error, {rebar_prv_common_test, Error}} = rebar_prv_common_test:prepare_tests(State), + {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State), + + false = lists:member(Opt, TestOpts). + +cfg_cover_spec(Config) -> + C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_cover_spec_opts_"), + + AppDir = ?config(apps, C), - {badconfig, "Test specs not supported"} = Error. + Name = rebar_test_utils:create_random_name("ct_cfg_cover_spec_opts_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + RebarConfig = [{ct_opts, [Opt = {cover, "spec/foo.spec"}]}], + + {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return), + + {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State), + + false = lists:member(Opt, TestOpts). cfg_atom_suites(Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_atom_suites_"), @@ -1116,9 +1181,10 @@ misspecified_ct_first_files(Config) -> {badconfig, {"Value `~p' of option `~p' must be a list", {some_file, ct_first_files}}} = Error. + %% helper for generating test data test_suite(Name) -> io_lib:format("-module(~ts_SUITE).\n" "-compile(export_all).\n" "all() -> [some_test].\n" - "some_test(_) -> ok.\n", [Name]).
\ No newline at end of file + "some_test(_) -> ok.\n", [Name]). diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl index fcc46c3..c95854a 100644 --- a/test/rebar_deps_SUITE.erl +++ b/test/rebar_deps_SUITE.erl @@ -3,7 +3,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, {group, git}, {group, pkg}]. +all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, semver_matching_lt, semver_matching_lte, semver_matching_gt, valid_version, {group, git}, {group, pkg}]. groups() -> [{all, [], [flat, pick_highest_left, pick_highest_right, @@ -29,6 +29,14 @@ init_per_group(_, Config) -> end_per_group(_, Config) -> Config. +init_per_testcase(valid_version, Config) -> + rebar_test_utils:init_rebar_state(Config); +init_per_testcase(semver_matching_lt, Config) -> + rebar_test_utils:init_rebar_state(Config); +init_per_testcase(semver_matching_lte, Config) -> + rebar_test_utils:init_rebar_state(Config); +init_per_testcase(semver_matching_gt, Config) -> + rebar_test_utils:init_rebar_state(Config); init_per_testcase(newly_added_after_empty_lock, Config) -> rebar_test_utils:init_rebar_state(Config); init_per_testcase(newly_added_dep, Config) -> @@ -49,14 +57,14 @@ init_per_testcase(http_proxy_settings, Config) -> %% Insert proxy variables into config rebar_test_utils:create_config(GlobalConfigDir, [{http_proxy, "http://localhost:1234"} - ]), + ]), rebar_test_utils:init_rebar_state(Config); init_per_testcase(https_proxy_settings, Config) -> SupportsHttpsProxy = case erlang:system_info(otp_release) of - "R16"++_ -> true; - "R"++_ -> false; - _ -> true % 17 and up don't have a "R" in the version - end, + "R16"++_ -> true; + "R"++_ -> false; + _ -> true % 17 and up don't have a "R" in the version + end, if not SupportsHttpsProxy -> {skip, https_proxy_unsupported_before_R16}; SupportsHttpsProxy -> @@ -73,20 +81,20 @@ init_per_testcase(https_proxy_settings, Config) -> %% Insert proxy variables into config rebar_test_utils:create_config(GlobalConfigDir, [{https_proxy, "http://localhost:1234"} - ]), + ]), rebar_test_utils:init_rebar_state(Config) end; init_per_testcase(Case, Config) -> {Deps, Warnings, Expect} = deps(Case), Expected = case Expect of - {ok, List} -> {ok, format_expected_deps(List)}; - {error, Reason} -> {error, Reason} - end, + {ok, List} -> {ok, format_expected_deps(List)}; + {error, Reason} -> {error, Reason} + end, DepsType = ?config(deps_type, Config), mock_warnings(), [{expect, Expected}, {warnings, Warnings} - | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))]. + | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))]. end_per_testcase(https_proxy_settings, Config) -> meck:unload(rebar_dir), @@ -100,8 +108,8 @@ end_per_testcase(_, Config) -> format_expected_deps(Deps) -> [case Dep of - {N,V} -> {dep, N, V}; - N -> {dep, N} + {N,V} -> {dep, N, V}; + N -> {dep, N} end || Dep <- Deps]. %% format: @@ -208,7 +216,7 @@ sub_app_deps(Config) -> SubAppsDir = filename:join([AppDir, "apps", Name]), SubDeps = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []} - ,{"b", "2.0.0", []}])), + ,{"b", "2.0.0", []}])), rebar_test_utils:create_app(SubAppsDir, Name, Vsn, [kernel, stdlib]), rebar_test_utils:create_config(SubAppsDir, [{deps, SubDeps}]), @@ -242,12 +250,12 @@ newly_added_dep(Config) -> %% Add a and c to top level TopDeps2 = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []} - ,{"c", "2.0.0", []} - ,{"b", "1.0.0", []}])), + ,{"c", "2.0.0", []} + ,{"b", "1.0.0", []}])), {ok, RebarConfig2} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps2}])), LockFile = filename:join(AppDir, "rebar.lock"), RebarConfig3 = rebar_config:merge_locks(RebarConfig2, - rebar_config:consult_lock_file(LockFile)), + rebar_config:consult_lock_file(LockFile)), %% a should now be installed and c should not change rebar_test_utils:run_and_check( @@ -277,7 +285,7 @@ newly_added_after_empty_lock(Config) -> {ok, RebarConfig2} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps2}])), LockFile = filename:join(AppDir, "rebar.lock"), RebarConfig3 = rebar_config:merge_locks(RebarConfig2, - rebar_config:consult_lock_file(LockFile)), + rebar_config:consult_lock_file(LockFile)), %% a should now be installed and c should not change rebar_test_utils:run_and_check( @@ -304,6 +312,74 @@ https_proxy_settings(_Config) -> httpc:get_option(https_proxy, rebar)). +semver_matching_lt(_Config) -> + Dep = <<"test">>, + Dep1 = {Dep, <<"1.0.0">>, Dep}, + MaxVsn = <<"0.2.0">>, + Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>], + ?assertEqual([{Dep, <<"0.1.9">>}], + rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1, + fun ec_semver:lt/2)). + +semver_matching_lte(_Config) -> + Dep = <<"test">>, + Dep1 = {Dep, <<"1.0.0">>, Dep}, + MaxVsn = <<"0.2.0">>, + Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>], + ?assertEqual([{Dep, <<"0.2.0">>}], + rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1, + fun ec_semver:lte/2)). + +semver_matching_gt(_Config) -> + Dep = <<"test">>, + Dep1 = {Dep, <<"1.0.0">>, Dep}, + MaxVsn = <<"0.2.0">>, + Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>], + ?assertEqual([{Dep, <<"0.2.1">>}], + rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1, + fun ec_semver:gt/2)). +semver_matching_gte(_Config) -> + Dep = <<"test">>, + Dep1 = {Dep, <<"1.0.0">>, Dep}, + MaxVsn = <<"0.2.0">>, + Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>], + ?assertEqual([{Dep, <<"0.2.0">>}], + rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1, + fun ec_semver:gt/2)). + +valid_version(_Config) -> + ?assert(rebar_prv_update:valid_vsn(<<"0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<"0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<">0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<">0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<=0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<=0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<">=0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<">=0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"==0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<"==0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"~>0.1">>)), + ?assert(rebar_prv_update:valid_vsn(<<"~>0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)), + ?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)), + ?assertNot(rebar_prv_update:valid_vsn(<<"> 0.1.0 and < 0.2.0">>)), + ok. + + run(Config) -> {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), rebar_test_utils:run_and_check( diff --git a/test/rebar_dir_SUITE.erl b/test/rebar_dir_SUITE.erl index 1221db7..9734830 100644 --- a/test/rebar_dir_SUITE.erl +++ b/test/rebar_dir_SUITE.erl @@ -6,6 +6,7 @@ -export([src_dirs/1, extra_src_dirs/1, all_src_dirs/1]). -export([profile_src_dirs/1, profile_extra_src_dirs/1, profile_all_src_dirs/1]). -export([retarget_path/1, alt_base_dir_abs/1, alt_base_dir_rel/1]). +-export([global_cache_dir/1, default_global_cache_dir/1, overwrite_default_global_cache_dir/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -15,8 +16,20 @@ all() -> [default_src_dirs, default_extra_src_dirs, default_all_src_dirs, src_dirs, extra_src_dirs, all_src_dirs, profile_src_dirs, profile_extra_src_dirs, profile_all_src_dirs, - retarget_path, alt_base_dir_abs, alt_base_dir_rel]. - + retarget_path, alt_base_dir_abs, alt_base_dir_rel, global_cache_dir, + default_global_cache_dir, overwrite_default_global_cache_dir]. + +init_per_testcase(default_global_cache_dir, Config) -> + [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, _State} | Config] = rebar_test_utils:init_rebar_state(Config), + NewState = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])} + ,{root_dir, AppsDir}]), + [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, NewState} | Config]; +init_per_testcase(overwrite_default_global_cache_dir, Config) -> + os:putenv("REBAR_CACHE_DIR", ?config(priv_dir, Config)), + [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, _State} | Config] = rebar_test_utils:init_rebar_state(Config), + NewState = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])} + ,{root_dir, AppsDir}]), + [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, NewState} | Config]; init_per_testcase(_, Config) -> C = rebar_test_utils:init_rebar_state(Config), AppDir = ?config(apps, C), @@ -162,3 +175,22 @@ alt_base_dir_rel(Config) -> ?assert(filelib:is_dir(filename:join([BaseDir, "lib", Name2, "ebin"]))), ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".app"]))), ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".beam"]))). + +global_cache_dir(Config) -> + RebarConfig = [{erl_opts, []}], + {ok, State} = rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], return), + DataDir = ?config(priv_dir, Config), + Expected = filename:join([DataDir, "cache"]), + ?assertEqual(Expected, rebar_dir:global_cache_dir(rebar_state:opts(State))). + +default_global_cache_dir(Config) -> + RebarConfig = [{erl_opts, []}], + {ok, State} = rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], return), + Expected = filename:join([rebar_dir:home_dir(), ".cache", "rebar3"]), + ?assertEqual(Expected, rebar_dir:global_cache_dir(rebar_state:opts(State))). + +overwrite_default_global_cache_dir(Config) -> + RebarConfig = [{erl_opts, []}], + {ok, State} = rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], return), + Expected = ?config(priv_dir, Config), + ?assertEqual(Expected, rebar_dir:global_cache_dir(rebar_state:opts(State))). diff --git a/test/rebar_dist_utils_SUITE.erl b/test/rebar_dist_utils_SUITE.erl new file mode 100644 index 0000000..e190b94 --- /dev/null +++ b/test/rebar_dist_utils_SUITE.erl @@ -0,0 +1,74 @@ +%%% This suite currently only tests for options parsing since we do +%%% not know if epmd will be running to actually boot nodes. +-module(rebar_dist_utils_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-compile(export_all). + +all() -> [from_config, from_cli, overlap, from_config_profile]. + +init_per_testcase(_, Config0) -> + Config = rebar_test_utils:init_rebar_state(Config0), + AppDir = ?config(apps, Config), + Name = rebar_test_utils:create_random_name("app_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(filename:join([AppDir,"apps",Name]), Name, Vsn, [kernel, stdlib]), + Config. + + +end_per_testcase(_, _) -> + ok. + +from_config(Config) -> + ShortConfig = [{dist_node, [{sname, 'a@localhost'}, {setcookie, abc}]}], + LongConfig = [{dist_node, [{name, 'a@localhost.x'}, {setcookie, abc}]}], + BothConfig = [{dist_node, [{sname, 'a@localhost'}, {name, 'a@localhost.x'}, {setcookie,abc}]}], + NoConfig = [], + CookieConfig = [{dist_node, [{setcookie, def}]}], + NoCookie = [{dist_node, [{sname, 'a@localhost'}]}], + {ok, State0} = rebar_test_utils:run_and_check(Config, ShortConfig, ["version"], return), + {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State0), + {ok, State1} = rebar_test_utils:run_and_check(Config, LongConfig, ["version"], return), + {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State1), + %% only support the first name found, side-effect of wanting profile support + {ok, State2} = rebar_test_utils:run_and_check(Config, BothConfig, ["version"], return), + {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State2), + {ok, State3} = rebar_test_utils:run_and_check(Config, NoConfig, ["version"], return), + {undefined, undefined, []} = rebar_dist_utils:find_options(State3), + {ok, State4} = rebar_test_utils:run_and_check(Config, CookieConfig, ["version"], return), + {undefined, undefined, [{setcookie, def}]} = rebar_dist_utils:find_options(State4), + {ok, State5} = rebar_test_utils:run_and_check(Config, NoCookie, ["version"], return), + {undefined, 'a@localhost', []} = rebar_dist_utils:find_options(State5), + ok. + +from_cli(Config) -> + {ok, State0} = rebar_test_utils:run_and_check(Config, [], ["version"], return), + {undefined, undefined, []} = rebar_dist_utils:find_options(State0), + State1 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}, {setcookie,abc}], []}), + {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State1), + State2 = rebar_state:command_parsed_args(State0, {[{name, 'a@localhost.x'}, {setcookie,abc}], []}), + {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State2), + State3 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}, {name, 'a@localhost.x'}, {setcookie,abc}], []}), + {'a@localhost.x', 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State3), + State4 = rebar_state:command_parsed_args(State0, {[{setcookie,def}], []}), + {undefined, undefined, [{setcookie, def}]} = rebar_dist_utils:find_options(State4), + State5 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}], []}), + {undefined, 'a@localhost', []} = rebar_dist_utils:find_options(State5), + ok. + +overlap(Config) -> + %% Make sure that CLI config takes over rebar config without clash for names, though + %% cookies can pass through + RebarConfig = [{dist_node, [{sname, 'a@localhost'}, {setcookie, abc}]}], + {ok, State0} = rebar_test_utils:run_and_check(Config, RebarConfig, ["version"], return), + State1 = rebar_state:command_parsed_args(State0, {[{name, 'b@localhost.x'}], []}), + {'b@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State1), + ok. + +from_config_profile(Config) -> + %% running as a profile does not create name clashes + RebarConfig = [{dist_node, [{sname, 'a@localhost'}, {setcookie, abc}]}, + {profiles, [ {fake, [{dist_node, [{name, 'a@localhost.x'}]}]} ]}], + {ok, State0} = rebar_test_utils:run_and_check(Config, RebarConfig, ["as","fake","version"], return), + {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State0), + ok. diff --git a/test/rebar_eunit_SUITE.erl b/test/rebar_eunit_SUITE.erl index cb2c911..41ab6ff 100644 --- a/test/rebar_eunit_SUITE.erl +++ b/test/rebar_eunit_SUITE.erl @@ -2,8 +2,12 @@ -export([all/0, groups/0]). -export([init_per_suite/1, init_per_group/2, end_per_group/2]). --export([basic_app_compiles/1, basic_app_files/1, basic_app_exports/1, basic_app_testset/1]). --export([multi_app_compiles/1, multi_app_files/1, multi_app_exports/1, multi_app_testset/1]). +-export([basic_app_compiles/1, basic_app_files/1]). +-export([basic_app_exports/1, basic_app_testset/1]). +-export([basic_app_eunit_macro/1]). +-export([multi_app_compiles/1, multi_app_files/1]). +-export([multi_app_exports/1, multi_app_testset/1]). +-export([multi_app_eunit_macro/1]). -export([eunit_tests/1, eunit_opts/1, eunit_first_files/1]). -export([single_application_arg/1, multi_application_arg/1, missing_application_arg/1]). -export([single_module_arg/1, multi_module_arg/1, missing_module_arg/1]). @@ -27,9 +31,15 @@ all() -> groups() -> [{basic_app, [sequence], [basic_app_compiles, {group, basic_app_results}]}, - {basic_app_results, [], [basic_app_files, basic_app_exports, basic_app_testset]}, + {basic_app_results, [], [basic_app_files, + basic_app_exports, + basic_app_testset, + basic_app_eunit_macro]}, {multi_app, [sequence], [multi_app_compiles, {group, multi_app_results}]}, - {multi_app_results, [], [multi_app_files, multi_app_exports, multi_app_testset]}, + {multi_app_results, [], [multi_app_files, + multi_app_exports, + multi_app_testset, + multi_app_eunit_macro]}, {cmd_line_args, [], [eunit_tests, eunit_opts, eunit_first_files, single_application_arg, multi_application_arg, missing_application_arg, single_module_arg, multi_module_arg, missing_module_arg, @@ -160,7 +170,16 @@ basic_app_testset(Config) -> {module, basic_app_tests_helper}]}, Set = rebar_prv_eunit:prepare_tests(Result). - +basic_app_eunit_macro(_Config) -> + Macro = fun(Mod) -> + begin + Path = code:which(Mod), + {ok, {Mod, [{compile_info, CompileInfo}]}} = beam_lib:chunks(Path, [compile_info]), + Opts = proplists:get_value(options, CompileInfo, []), + true = lists:member({d, 'EUNIT'}, Opts) + end + end, + lists:foreach(Macro, [basic_app, basic_app_tests, basic_app_tests_helper]). %% === tests for multiple applications in the `apps' directory of a project === @@ -220,7 +239,19 @@ multi_app_testset(Config) -> {module, multi_app_tests_helper}]}, Set = rebar_prv_eunit:prepare_tests(Result). - +multi_app_eunit_macro(_Config) -> + Macro = fun(Mod) -> + begin + Path = code:which(Mod), + {ok, {Mod, [{compile_info, CompileInfo}]}} = beam_lib:chunks(Path, [compile_info]), + Opts = proplists:get_value(options, CompileInfo, []), + true = lists:member({d, 'EUNIT'}, Opts) + end + end, + lists:foreach(Macro, [multi_app_bar, multi_app_bar_tests, + multi_app_baz, multi_app_baz_tests, + multi_app_tests, multi_app_tests_helper, + multi_app_bar_tests_helper, multi_app_baz_tests_helper]). %% === tests for command line arguments === diff --git a/test/rebar_hooks_SUITE.erl b/test/rebar_hooks_SUITE.erl index 188fb34..b121dd5 100644 --- a/test/rebar_hooks_SUITE.erl +++ b/test/rebar_hooks_SUITE.erl @@ -10,6 +10,7 @@ escriptize_artifacts/1, run_hooks_once/1, run_hooks_for_plugins/1, + eunit_app_hooks/1, deps_hook_namespace/1]). -include_lib("common_test/include/ct.hrl"). @@ -33,7 +34,7 @@ end_per_testcase(_, _Config) -> all() -> [build_and_clean_app, run_hooks_once, escriptize_artifacts, - run_hooks_for_plugins, deps_hook_namespace]. + run_hooks_for_plugins, deps_hook_namespace, eunit_app_hooks]. %% Test post provider hook cleans compiled project app, leaving it invalid build_and_clean_app(Config) -> @@ -119,6 +120,25 @@ deps_hook_namespace(Config) -> {ok, [{dep, "some_dep"}]} ). +%% Checks that a hook that is defined on an app (not a top level hook of a project with subapps) is run +eunit_app_hooks(Config) -> + AppDir = ?config(apps, Config), + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + RConfFile = + rebar_test_utils:create_config(AppDir, + [ + {escript_name, list_to_atom(Name)} + ,{provider_hooks, [{post, [{eunit, escriptize}]}]} + ]), + {ok, RConf} = file:consult(RConfFile), + + rebar_test_utils:run_and_check(Config, RConf, + ["eunit"], {ok, [{app, Name, valid} + ,{file, filename:join([AppDir, "_build/test/bin", Name])}]}). + run_hooks_for_plugins(Config) -> AppDir = ?config(apps, Config), diff --git a/test/rebar_lock_SUITE.erl b/test/rebar_lock_SUITE.erl new file mode 100644 index 0000000..00875f7 --- /dev/null +++ b/test/rebar_lock_SUITE.erl @@ -0,0 +1,46 @@ +%%% Most locking tests are implicit in other test suites handling +%%% dependencies. +%%% This suite is to test the compatibility layers between various +%%% versions of lockfiles. +-module(rebar_lock_SUITE). +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +all() -> [current_version, future_versions_no_attrs, future_versions_attrs]. + +current_version(Config) -> + %% Current version just dumps the locks as is on disk. + LockFile = filename:join(?config(priv_dir, Config), "current_version"), + Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + file:write_file(LockFile, io_lib:format("~p.~n", [Locks])), + ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). + +future_versions_no_attrs(Config) -> + %% Future versions will keep the same core attribute in there, but + %% will do so under a new format bundled with a version and potentially + %% some trailing attributes + LockFile = filename:join(?config(priv_dir, Config), "future_versions"), + Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + LockData = {"3.5.2", Locks}, + file:write_file(LockFile, io_lib:format("~p.~n", [LockData])), + ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). + +future_versions_attrs(Config) -> + %% Future versions will keep the same core attribute in there, but + %% will do so under a new format bundled with a version and potentially + %% some trailing attributes + LockFile = filename:join(?config(priv_dir, Config), "future_versions"), + Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + LockData = {"3.5.2", Locks}, + file:write_file(LockFile, io_lib:format("~p.~na.~n{b,c}.~n[d,e,f].~n", [LockData])), + ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl index 9f19e0d..6a75f32 100644 --- a/test/rebar_pkg_SUITE.erl +++ b/test/rebar_pkg_SUITE.erl @@ -180,11 +180,14 @@ pkgs_provider(Config) -> find_highest_matching(_Config) -> State = rebar_state:new(), - {ok, Vsn} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"1.0.0">>, package_index, State), + {ok, Vsn} = rebar_packages:find_highest_matching( + <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0.0">>, package_index, State), ?assertEqual(<<"1.0.1">>, Vsn), - {ok, Vsn1} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"1.0">>, package_index, State), + {ok, Vsn1} = rebar_packages:find_highest_matching( + <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0">>, package_index, State), ?assertEqual(<<"1.1.1">>, Vsn1), - {ok, Vsn2} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"2.0">>, package_index, State), + {ok, Vsn2} = rebar_packages:find_highest_matching( + <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"2.0">>, package_index, State), ?assertEqual(<<"2.0.0">>, Vsn2). diff --git a/test/rebar_plugins_SUITE.erl b/test/rebar_plugins_SUITE.erl index c1a98de..a313683 100644 --- a/test/rebar_plugins_SUITE.erl +++ b/test/rebar_plugins_SUITE.erl @@ -11,8 +11,10 @@ complex_plugins/1, list/1, upgrade/1, + upgrade_project_plugin/1, sub_app_plugins/1, - sub_app_plugin_overrides/1]). + sub_app_plugin_overrides/1, + project_plugins/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -34,7 +36,8 @@ end_per_testcase(_, _Config) -> catch meck:unload(). all() -> - [compile_plugins, compile_global_plugins, complex_plugins, list, upgrade, sub_app_plugins, sub_app_plugin_overrides]. + [compile_plugins, compile_global_plugins, complex_plugins, list, upgrade, upgrade_project_plugin, + sub_app_plugins, sub_app_plugin_overrides, project_plugins]. %% Tests that compiling a project installs and compiles the plugins of deps compile_plugins(Config) -> @@ -211,6 +214,45 @@ upgrade(Config) -> {ok, [{app, Name}, {plugin, PkgName, <<"0.1.3">>}]} ). +upgrade_project_plugin(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + PkgName = rebar_test_utils:create_random_name("pkg1_"), + mock_git_resource:mock([]), + mock_pkg_resource:mock([ + {pkgdeps, [{{iolist_to_binary(PkgName), <<"0.1.0">>}, []}, + {{iolist_to_binary(PkgName), <<"0.0.1">>}, []}, + {{iolist_to_binary(PkgName), <<"0.1.1">>}, []}]} + ]), + + RConfFile = rebar_test_utils:create_config(AppDir, [{project_plugins, [list_to_atom(PkgName)]}]), + {ok, RConf} = file:consult(RConfFile), + + %% Build with deps. + rebar_test_utils:run_and_check( + Config, RConf, ["compile"], + {ok, [{app, Name}, {plugin, PkgName, <<"0.1.1">>}]} + ), + + catch mock_pkg_resource:unmock(), + mock_pkg_resource:mock([ + {pkgdeps, [{{iolist_to_binary(PkgName), <<"0.1.0">>}, []}, + {{iolist_to_binary(PkgName), <<"0.0.1">>}, []}, + {{iolist_to_binary(PkgName), <<"0.1.3">>}, []}, + {{iolist_to_binary(PkgName), <<"0.1.1">>}, []}]}, + {upgrade, [PkgName]} + ]), + + %% Build with deps. + rebar_test_utils:run_and_check( + Config, RConf, ["plugins", "upgrade", PkgName], + {ok, [{app, Name}, {plugin, PkgName, <<"0.1.3">>}]} + ). + sub_app_plugins(Config) -> AppDir = ?config(apps, Config), @@ -281,3 +323,50 @@ sub_app_plugin_overrides(Config) -> Config, RConf, ["compile"], {ok, [{app, Name}, {dep, Dep2Name, Vsn}, {plugin, DepName, Vsn2}, {plugin, PluginName}]} ). + +%% Check that project plugins are first in providers even if they override defaults but that +%% normal plugins do not +project_plugins(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + DepName = rebar_test_utils:create_random_name("dep1_"), + PluginName = "compile", + PluginName2 = "release", + + Plugins = rebar_test_utils:expand_deps(git, [{PluginName, Vsn, []}, {PluginName2, Vsn, []}]), + {SrcDeps, _} = rebar_test_utils:flat_deps(Plugins), + mock_git_resource:mock([{deps, SrcDeps}], create_plugin), + + mock_pkg_resource:mock([{pkgdeps, [{{list_to_binary(DepName), list_to_binary(Vsn)}, []}]}, + {config, [{plugins, [ + {list_to_atom(PluginName), + {git, "http://site.com/user/"++PluginName++".git", + {tag, Vsn}}}]}]}]), + + RConfFile = + rebar_test_utils:create_config(AppDir, + [{deps, [ + list_to_atom(DepName) + ]}, + {project_plugins, [ + {list_to_atom(PluginName2), + {git, "http://site.com/user/"++PluginName2++".git", + {tag, Vsn}}}]}]), + {ok, RConf} = file:consult(RConfFile), + + %% Build with deps. + {ok, State} = rebar_test_utils:run_and_check( + Config, RConf, ["compile"], + {ok, [{app, Name}, {plugin, PluginName}, {plugin, PluginName2}, {dep, DepName}]} + ), + + %% Should have 2 release providers but only 1 compile provider + Release = [P || P <- rebar_state:providers(State), providers:impl(P) =:= release, providers:namespace(P) =:= default], + Compile = [P || P <- rebar_state:providers(State), providers:impl(P) =:= compile, providers:namespace(P) =:= default], + + ?assertEqual(length(Release), 2), + ?assertEqual(length(Compile), 1). diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index 5187bda..23b0178 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -3,7 +3,7 @@ -include_lib("eunit/include/eunit.hrl"). -export([init_rebar_state/1, init_rebar_state/2, run_and_check/4, check_results/3]). -export([expand_deps/2, flat_deps/1, top_level_deps/1]). --export([create_app/4, create_eunit_app/4, create_empty_app/4, +-export([create_app/4, create_plugin/4, create_eunit_app/4, create_empty_app/4, create_config/2, create_config/3, package_app/3]). -export([create_random_name/1, create_random_vsn/0, write_src_file/2]). @@ -82,6 +82,16 @@ create_app(AppDir, Name, Vsn, Deps) -> write_app_src_file(AppDir, Name, Vsn, Deps), rebar_app_info:new(Name, Vsn, AppDir, Deps). +%% @doc Creates a dummy plugin including: +%% - src/<file>.erl +%% - src/<file>.app.src +%% And returns a `rebar_app_info' object. +create_plugin(AppDir, Name, Vsn, Deps) -> + write_plugin_file(AppDir, Name ++ ".erl"), + write_src_file(AppDir, "not_a_real_src_" ++ Name ++ ".erl"), + write_app_src_file(AppDir, Name, Vsn, Deps), + rebar_app_info:new(Name, Vsn, AppDir, Deps). + %% @doc Creates a dummy application including: %% - src/<file>.erl %% - src/<file>.app.src @@ -365,6 +375,11 @@ check_results(AppDir, Expected, ProfileRun) -> ?assert(filelib:is_dir(Dirname)) end, Expected). +write_plugin_file(Dir, Name) -> + Erl = filename:join([Dir, "src", Name]), + ok = filelib:ensure_dir(Erl), + ok = ec_file:write(Erl, plugin_src_file(Name)). + write_src_file(Dir, Name) -> Erl = filename:join([Dir, "src", Name]), ok = filelib:ensure_dir(Erl), @@ -395,6 +410,18 @@ erl_src_file(Name) -> "-export([main/0]).\n" "main() -> ok.\n", [filename:basename(Name, ".erl")]). +plugin_src_file(Name) -> + io_lib:format("-module('~s').\n" + "-export([init/1]).\n" + "init(State) -> \n" + "Provider = providers:create([\n" + "{name, '~s'},\n" + "{module, '~s'}\n" + "]),\n" + "{ok, rebar_state:add_provider(State, Provider)}.\n", [filename:basename(Name, ".erl"), + filename:basename(Name, ".erl"), + filename:basename(Name, ".erl")]). + erl_eunitized_src_file(Name) -> io_lib:format("-module('~s').\n" "-export([main/0]).\n" |