diff options
56 files changed, 1243 insertions, 624 deletions
diff --git a/.travis.yml b/.travis.yml index b0729f8..d1163c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ +sudo: false language: erlang otp_release: - - 17.0 + - 18.0 + - 17.5 - R16B03-1 - R15B03 before_script: "./bootstrap" @@ -8,6 +10,9 @@ script: "./rebar3 ct" branches: only: - master +cache: + directories: + - $HOME/.cache/rebar3/hex/com/amazonaws/s3/s3.hex.pm/tarballs/packages/ before_deploy: "rm -rf !(rebar3)" deploy: on: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0de0eb..0ce1e93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Contributing to rebar --------------------- -Before implementing a new feature, please submit a ticket to discuss your plans. +Before implementing a new feature, please submit a ticket to discuss your plans. The feature might have been rejected already, or the implementation might already be decided. See [Community and Resources](README.md#community-and-resources). @@ -9,95 +9,99 @@ See [Community and Resources](README.md#community-and-resources). Code style ---------- -The following rules must be followed: +The following rules apply: * Do not introduce trailing whitespace - * Do not mix spaces and tabs - * Do not introduce lines longer than 80 characters - -The following rules should be followed: + * We use spaces for indenting only + * Try not to introduce lines longer than 80 characters * Write small functions whenever possible - * Avoid having too many clauses containing clauses containing clauses. + * Avoid having too many clauses containing clauses containing clauses. Basically, avoid deeply nested functions. -[erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) -indentation is preferred. This will keep the code base consistent. -vi users are encouraged to give [Vim emulation](http://emacswiki.org/emacs/Evil) ([more -info](https://gitorious.org/evil/pages/Home)) a try. +Follow the indentation style of existing files. The [erlang-mode for +(emacs)](http://www.erlang.org/doc/man/erlang.el.html) indentation is going to +always work. Other users may want to use 4-width spaces and make sure things +align mostly the way they would with Emacs code, or with the rest of the +project. + +Where possible, include type specifications for your code so type analysis +will be as accurate as possible. + +Please add comments around tricky fixes or workarounds so that we can +easily know why they're there at a glance. Pull requests and branching --------------------------- -Use one topic branch per pull request. If you do that, you can add extra commits or fix up -buggy commits via `git rebase -i`, and update the branch. The updated branch will be -visible in the same pull request. Therefore, you should not open a new pull request when -you have to fix your changes. +All fixes to rebar end up requiring a +1 from one or more of the project's +maintainers. When opening a pull request, explain what the patch is doing +and if it makes sense, why the proposed implementation was chosen. + +Try to use well-defined commits (one feature per commit) so that reading +them and testing them is easier for reviewers and while bissecting the code +base for issues. + +During the review process, you may be asked to correct or edit a few things +before a final rebase to merge things. -Do not commit to master in your fork. +Please work in feature branches, and do not commit to `master` in your fork. Provide a clean branch without merge commits. Tests ----- -As a general rule, any behavioral change to rebar requires a test to go with it. If there's -already a test case, you may have to modify that one. If there isn't a test case or a test -suite, add a new test case or suite in `inttest/`. [retest](https://github.com/dizzyd/retest) based tests are preferred, but -we also have EUnit tests in `test/`. +As a general rule, any behavioral change to rebar requires a test to go with +it. If there's already a test case, you may have to modify that one. If there +isn't a test case or a test suite, add a new test case or suite in `test/`. -Say you've added a new test case in `inttest/erlc`. To only execute the modified suite, -you would do the following: -```sh -# First we build rebar and its deps to also get `deps/retest/retest` -$ make debug deps -# Now we can test the modified erlc suite -$ deps/retest/retest -v inttest/erlc -``` +To run the tests: -To test EUnit tests, you would do: ```sh -$ make debug -$ ./rebar -v eunit +$ ./bootstrap +$ ./rebar3 ct ``` -You can also run `make test` to execute both EUnit and [retest](https://github.com/dizzyd/retest) tests as `make check` does. +The rebar3 test suite is written using Common Test. As many of the tests as +possible are written by using the programmatic rebar3 API rather than +by running the escriptized project directly. The tests should be restricted +to their `priv_dir` and leave the system clean after a run. + + If such tests prove hard to write, you can ask for help doing that in your +pull request. + +For tests having a lot to do with I/O and terminal interaction, consider +adding them to https://github.com/tsloughter/rebar3_tests + Credit ------ -To give everyone proper credit in addition to the git history, please feel free to append +To give everyone proper credit in addition to the git history, please feel free to append your name to `THANKS` in your first contribution. Committing your changes ----------------------- -Please ensure that all commits pass all tests, and do not have extra Dialyzer warnings. -To do that run `make check`. If you didn't build via `make debug` at first, the beam files in -`ebin/` might be missing debug_info required for [xref](http://www.erlang.org/doc/man/xref.html) -and [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html), causing a test -failure. -If that happens, running `make clean` before running `make check` could solve the problem. -If you change any of the files with known but safe to ignore Dialyzer warnings, you may -have to adapt the line number(s) in [dialyzer_reference](dialyzer_reference). If you do that, -do not remove the -leading blank line. +Please ensure that all commits pass all tests, and do not have extra Dialyzer warnings. +To do that run `./rebar3 ct` and `./rebar3 as dialyze dialyzer`. #### Structuring your commits -Fixing a bug is one commit. -Adding a feature is one commit. -Adding two features is two commits. -Two unrelated changes is two commits. +- Fixing a bug is one commit. +- Adding a feature is one commit. +- Adding two features is two commits. +- Two unrelated changes is two commits (and likely two Pull requests) -If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup commit into +If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup commit into the original commit. #### Writing Commit Messages -It's important to write a proper commit title and description. The commit title must be -at most 50 characters; it is the first line of the commit text. The second line of the -commit text must be left blank. The third line and beyond is the commit message. You -should write a commit message. If you do, wrap all lines at 72 characters. You should -explain what the commit does, what references you used, and any other information +It's important to write a proper commit title and description. The commit title must be +at most 50 characters; it is the first line of the commit text. The second line of the +commit text must be left blank. The third line and beyond is the commit message. You +should write a commit message. If you do, wrap all lines at 72 characters. You should +explain what the commit does, what references you used, and any other information that helps understanding your changes. Basically, structure your commit message like this: @@ -11,21 +11,13 @@ embed directly in a project. Where possible, rebar uses standard Erlang/OTP conventions for project structures, thus minimizing the amount of build configuration work. rebar also provides dependency management, enabling application writers to easily re-use common libraries from a variety of -locations (git, hg, etc). +locations ([hex.pm](http://hex.pm), git, hg, and so on). -3.0 Alpha-6 +3.0 Beta-1 ==== [DOCUMENTATION](http://www.rebar3.org/v3.0/docs) -This is a preliminary branch, considered to be alpha, and still -very unstable. Use at your own risk, but please do report bugs -and issues encountered and we'll try to resolve problems as -soon as possible. - -Compatibility with rebar 2.0 has been broken, and features removed to -limit scope. - ### Commands | Command | Description | @@ -35,32 +27,47 @@ limit scope. | clean | Remove project apps beam files | | cover | Generate coverage info from data compiled by `eunit --cover` or `ct --cover` | | ct | Run Common Test suites | +| deps | Lists dependencies currently in use | | do | Higher-order provider to run multiple tasks in sequence | | dialyzer | Run the Dialyzer analyzer on the project | | edoc | Generate documentation using edoc | -| eunit | Run eunit tests | | escriptize | Generate escript of project | +| eunit | Run eunit tests | | help | Print help for rebar or task | | new | Create new rebar project from templates | | pkgs | List available packages | +| plugins | List or upgrade plugins | | release | Build release of project | +| relup | Creates relup from 2 releases | | report | Report on environment and versions for bug reports | | shell | Run shell with project apps in path | | tar | Package release into tarball | +| unlock | Unlock dependencies | | update | Update package index | | upgrade | Fetch latest version of dep | | version | Print current version of Erlang/OTP and rebar | | xref | Run cross reference analysis on the project | +A more complete list can be found on the [docs page](http://www.rebar3.org/v3.0/docs/commands) + ### Changes +#### Since Rebar 2.x + * Fetches and builds deps if missing when running any command that relies on them * Automatically recognizes `apps` and `lib` directory structure -* Relx for releases +* Relx for releases and relups +* deterministic builds and conflict resolution +* New plugin handling mechanism +* New hook mechanism +* Support for packages +* A ton more ### Gone * Reltool integeration +* Quickcheck integration (moved to [a plugin](http://www.rebar3.org/v3.0/docs/using-available-plugins#quickcheck)) +* C code compiling (moved to [a plugin](http://www.rebar3.org/v3.0/docs/using-available-plugins#port-compiler) or hooks) ### Providers @@ -141,7 +148,6 @@ $ ./rebar3 escriptize $ _build/default/bin/rebar3 ``` - Contributing to rebar ===================== @@ -159,15 +165,15 @@ The main place to go for questions is the [rebar mailing list](http://lists.basho.com/pipermail/rebar_lists.basho.com/). If you need quick feedback, you can try the #rebar channel on [irc.freenode.net](http://freenode.net). Be sure to check the -[wiki](https://github.com/rebar/rebar/wiki) first, just to be sure you're not +[documentation](http://www.rebar3.org/v3.0/docs) first, just to be sure you're not asking about things with well known answers. For bug reports, roadmaps, and issues, visit the [github issues -page](https://github.com/rebar/rebar/issues). +page](https://github.com/rebar/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/) -- [wiki](https://github.com/rebar/rebar/wiki) -- [issues](https://github.com/rebar/rebar/issues) +- [issues](https://github.com/rebar/rebar3/issues) +- [Documentation](http://www.rebar3.org/v3.0/docs) @@ -132,4 +132,6 @@ Tristan Sloughter Kelly McLaughlin Martin Karlsson Pierre Fenoll -David Kubecka
\ No newline at end of file +David Kubecka +Stefan Grundmann +Carlos Eduardo de Paula @@ -4,6 +4,14 @@ main(_Args) -> + application:start(crypto), + application:start(asn1), + application:start(public_key), + application:start(ssl), + inets:start(), + inets:start(httpc, [{profile, rebar}]), + set_httpc_options(), + %% Fetch and build deps required to build rebar3 BaseDeps = [{providers, []} ,{getopt, []} @@ -24,6 +32,7 @@ main(_Args) -> setup_env(), os:putenv("REBAR_PROFILE", "bootstrap"), + rebar3:run(["update"]), {ok, State} = rebar3:run(["compile"]), reset_env(), os:putenv("REBAR_PROFILE", ""), @@ -56,28 +65,58 @@ fetch_and_compile({Name, ErlFirstFiles}, Deps) -> ok = fetch(Repo, Name), compile(Name, ErlFirstFiles). -fetch({git, Url, Source}, App) -> +fetch({pkg, Name, Vsn}, App) -> Dir = filename:join([filename:absname("_build/default/lib/"), App]), - case filelib:is_dir(Dir) of - true -> - true = code:add_path(filename:join(Dir, "ebin")), - ok; - false -> - fetch_source(Dir, Url, Source), - ok + CDN = "https://s3.amazonaws.com/s3.hex.pm/tarballs", + Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>), + Url = string:join([CDN, Package], "/"), + case request(Url) of + {ok, Binary} -> + {ok, Contents} = extract(Binary), + ok = erl_tar:extract({binary, Contents}, [{cwd, Dir}, compressed]); + _ -> + io:format("Error: Unable to fetch package ~p ~p~n", [Name, Vsn]) + end. + +extract(Binary) -> + {ok, Files} = erl_tar:extract({binary, Binary}, [memory]), + {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files), + {ok, Contents}. + +request(Url) -> + case httpc:request(get, {Url, []}, + [{relaxed, true}], + [{body_format, binary}], + rebar) of + {ok, {{_Version, 200, _Reason}, _Headers, Body}} -> + {ok, Body}; + Error -> + Error end. -fetch_source(Dir, Url, {ref, Ref}) -> - ok = filelib:ensure_dir(Dir), - os:cmd(io_lib:format("git clone ~s ~s", [Url, Dir])), - {ok, Cwd} = file:get_cwd(), - file:set_cwd(Dir), - os:cmd(io_lib:format("git checkout -q ~s", [Ref])), - file:set_cwd(Cwd); -fetch_source(Dir, Url, {_, Branch}) -> - ok = filelib:ensure_dir(Dir), - os:cmd(io_lib:format("git clone ~s ~s -b ~s --single-branch", - [Url, Dir, Branch])). +get_rebar_config() -> + {ok, [[Home]]} = init:get_argument(home), + ConfDir = filename:join(Home, ".config/rebar3"), + case file:consult(filename:join(ConfDir, "rebar.config")) of + {ok, Config} -> + Config; + _ -> + [] + end. + +get_http_vars(Scheme) -> + proplists:get_value(Scheme, get_rebar_config(), []). + +set_httpc_options() -> + set_httpc_options(https_proxy, get_http_vars(https_proxy)), + set_httpc_options(proxy, get_http_vars(http_proxy)). + +set_httpc_options(_, []) -> + ok; + +set_httpc_options(Scheme, Proxy) -> + {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy), + httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar). compile(App, FirstFiles) -> Dir = filename:join(filename:absname("_build/default/lib/"), App), @@ -86,6 +125,7 @@ compile(App, FirstFiles) -> FirstFilesPaths = [filename:join([Dir, "src", Module]) || Module <- FirstFiles], Sources = FirstFilesPaths ++ filelib:wildcard(filename:join([Dir, "src", "*.erl"])), [compile_file(X, [{i, filename:join(Dir, "include")} + ,debug_info ,{outdir, filename:join(Dir, "ebin")} ,return | additional_defines()]) || X <- Sources]. @@ -219,6 +259,7 @@ setup_env() -> application:load(rebar), {ok, Providers} = application:get_env(rebar, providers), Providers1 = Providers -- [rebar_prv_release, + rebar_prv_relup, rebar_prv_tar], application:set_env(rebar, providers, Providers1). diff --git a/priv/shell-completion/bash/rebar3 b/priv/shell-completion/bash/rebar3 index b6d3de2..4e28d3d 100644 --- a/priv/shell-completion/bash/rebar3 +++ b/priv/shell-completion/bash/rebar3 @@ -23,7 +23,9 @@ _rebar3() help \ new \ pkgs \ + plugins \ release \ + relup \ report \ shell \ tar \ @@ -94,6 +96,8 @@ _rebar3() lopts="--force" elif [[ ${prev} == pkgs ]] ; then : + elif [[ ${prev} == plugins ]] ; then + : elif [[ ${prev} == release ]] ; then sopts="-n -v -g -u -o -h -l -p -V -d -i -a -c -r" lopts="--relname \ @@ -116,6 +120,28 @@ _rebar3() --system_libs \ --version \ --root" + elif [[ ${prev} == relup ]] ; then + sopts="-n -v -g -u -o -h -l -p -V -d -i -a -c -r" + lopts="--relname \ + --relvsn \ + --goal \ + --upfrom \ + --output-dir \ + --help \ + --lib-dir \ + --path \ + --default-libs \ + --verbose \ + --dev-mode \ + --include-erts \ + --override \ + --config \ + --overlay_vars \ + --vm_args \ + --sys_config \ + --system_libs \ + --version \ + --root" elif [[ ${prev} == report ]] ; then : elif [[ ${prev} == shell ]] ; then diff --git a/priv/shell-completion/zsh/_rebar3 b/priv/shell-completion/zsh/_rebar3 index 5fcdd91..b03b7c9 100644 --- a/priv/shell-completion/zsh/_rebar3 +++ b/priv/shell-completion/zsh/_rebar3 @@ -111,6 +111,11 @@ _rebar3 () { (pkgs) _message 'List available packages.' && ret=0 ;; + (plugins) + _arguments \ + '1:type:(list upgrade)' \ + && ret=0 + ;; (release) _arguments \ '(-n --relname)'{-n,--relname}'[Specify the name for the release that will be generated.]:relname' \ @@ -134,6 +139,29 @@ _rebar3 () { '(-r --root)'{-r,--root}'[The project root directory]:system libs:_files -/' \ && ret=0 ;; + (relup) + _arguments \ + '(-n --relname)'{-n,--relname}'[Specify the name for the release that will be generated.]:relname' \ + '(-v --relvsn)'{-n,--relname}'[Specify the version for the release.]:relvsn' \ + '(-g --goal)'{-g,--goal}'[Specify a target constraint on the system. These are usually the OTP.]:goal' \ + '(-u --upfrom)'{-u,--upfrom}'[Only valid with relup target, specify the release to upgrade from.]:upfrom' \ + '(-o --output-dir)'{-o,--output-dir}'[The output directory for the release. This is ./ by default.]:out directory:_files -/' \ + '(-l --lib-dir)'{-l,--output-dir}'[Additional dir that should be searched for OTP Apps]:lib directory:_files -/' \ + '(-p --path)'{-p,--path}'[Additional dir to add to the code path]:path directory:_files -/' \ + '(--default-libs)--default-libs[Whether to use the default system added lib dirs]:default libs:(true false)' \ + '(-V --verbose)'{-V,--verbose}'[Verbosity level, maybe between 0 and 3 ,default: 2]:verbosity level:(0 1 2 3)' \ + '(-d --dev-mode)'{-d,--dev-mode}'[Symlink the applications and configuration into the release instead of copying]' \ + '(-i --include-erts)'{-i,--dev-mode}'[If true include a copy of erts used to build with, if a path include erts at that path. If false, do not include erts]' \ + '(-a --override)'{-a,--override}'[Provide an app name and a directory to override in the form <appname>:<app directory>]:override' \ + '(-c --config)'{-c,--config}'[The path to a config file]:config file:_files ' \ + '(--overlay_vars)--overlay_vars[Path to a file of overlay variables]:overlay variables file:_files' \ + '(--vm_args)--vm_args[Path to a file to use for vm.args]:vm args file:_files' \ + '(--sys_config)--sys_config[Path to a file to use for sys.config]:sys config file:_files' \ + '(--system_libs)--system_libs[Path to dir of Erlang system libs]:system libs:_files -/' \ + '(--version)--version[Print relx version]' \ + '(-r --root)'{-r,--root}'[The project root directory]:system libs:_files -/' \ + && ret=0 + ;; (report) _arguments '1: :_rebar3_tasks' && ret=0 ;; @@ -202,11 +230,13 @@ _rebar3_tasks() { 'help:Display a list of tasks or help for a given task or subtask.' 'new:Create new project from templates.' 'pkgs:List available packages.' + 'plugins:List or upgrade plugins.' 'release:Build release of project.' + 'relup:Create relup from 2 releases.' 'report:Provide a crash report to be sent to the rebar3 issues page.' 'shell:Run shell with project apps and deps in path.' 'tar:Tar archive of release built of project.' - 'unlock:Unlock dependencies..' + 'unlock:Unlock dependencies.' 'update:Update package index.' 'upgrade:Upgrade dependencies.' 'version:Print version for rebar and current Erlang.' diff --git a/priv/templates/otp_app.app.src b/priv/templates/otp_app.app.src index 9ad7478..09e4a48 100644 --- a/priv/templates/otp_app.app.src +++ b/priv/templates/otp_app.app.src @@ -8,5 +8,9 @@ stdlib ]}, {env,[]}, - {modules, []} + {modules, []}, + + {contributors, []}, + {licenses, []}, + {links, []} ]}. diff --git a/priv/templates/otp_lib.app.src b/priv/templates/otp_lib.app.src index 3f4b56b..f07293e 100644 --- a/priv/templates/otp_lib.app.src +++ b/priv/templates/otp_lib.app.src @@ -7,5 +7,9 @@ stdlib ]}, {env,[]}, - {modules, []} + {modules, []}, + + {contributors, []}, + {licenses, []}, + {links, []} ]}. diff --git a/priv/templates/plugin.erl b/priv/templates/plugin.erl index 018dd0e..c6e5e40 100644 --- a/priv/templates/plugin.erl +++ b/priv/templates/plugin.erl @@ -1,33 +1,8 @@ -module('{{name}}'). --behaviour(provider). --export([init/1, do/1, format_error/1]). +-export([init/1]). --define(PROVIDER, '{{name}}'). --define(DEPS, [app_discovery]). - -%% =================================================================== -%% Public API -%% =================================================================== -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> - Provider = providers:create([ - {name, ?PROVIDER}, % The 'user friendly' name of the task - {module, ?MODULE}, % The module implementation of the task - {bare, true}, % The task can be run by the user, always true - {deps, ?DEPS}, % The list of dependencies - {example, "rebar3 {{name}}"}, % How to use the plugin - {opts, []}, % list of options understood by the plugin - {short_desc, "{{desc}}"}, - {desc, "{{desc}}"} - ]), - {ok, rebar_state:add_provider(State, Provider)}. - - --spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. -do(State) -> - {ok, State}. - --spec format_error(any()) -> iolist(). -format_error(Reason) -> - io_lib:format("~p", [Reason]). + {ok, State1} = '{{name}}_prv':init(State), + {ok, State1}. diff --git a/priv/templates/plugin.template b/priv/templates/plugin.template index 811be0b..c0e36de 100644 --- a/priv/templates/plugin.template +++ b/priv/templates/plugin.template @@ -4,6 +4,7 @@ {desc, "A rebar plugin", "Short description of the plugin's purpose"} ]}. {template, "plugin.erl", "{{name}}/src/{{name}}.erl"}. +{template, "provider.erl", "{{name}}/src/prv_{{name}}_prv.erl"}. {template, "otp_lib.app.src", "{{name}}/src/{{name}}.app.src"}. {template, "rebar.config", "{{name}}/rebar.config"}. {template, "gitignore", "{{name}}/.gitignore"}. diff --git a/priv/templates/provider.erl b/priv/templates/provider.erl new file mode 100644 index 0000000..669df83 --- /dev/null +++ b/priv/templates/provider.erl @@ -0,0 +1,32 @@ +-module('{{name}}_prv'). + +-export([init/1, do/1, format_error/1]). + +-define(PROVIDER, '{{name}}'). +-define(DEPS, [app_discovery]). + +%% =================================================================== +%% Public API +%% =================================================================== +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([ + {name, ?PROVIDER}, % The 'user friendly' name of the task + {module, ?MODULE}, % The module implementation of the task + {bare, true}, % The task can be run by the user, always true + {deps, ?DEPS}, % The list of dependencies + {example, "rebar3 {{name}}"}, % How to use the plugin + {opts, []}, % list of options understood by the plugin + {short_desc, "{{desc}}"}, + {desc, "{{desc}}"} + ]), + {ok, rebar_state:add_provider(State, Provider)}. + + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + {ok, State}. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). diff --git a/rebar.config b/rebar.config index d68f195..16b57d8 100644 --- a/rebar.config +++ b/rebar.config @@ -1,22 +1,11 @@ %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et -{deps, [ - {erlware_commons, "", - {git, "https://github.com/erlware/erlware_commons.git", - {branch, "master"}}}, - {providers, "", - {git, "https://github.com/tsloughter/providers.git", - {tag, "v1.4.0"}}}, - {relx, "", - {git, "https://github.com/erlware/relx.git", - {branch, "master"}}}, - {mustache, ".*", - {git, "https://github.com/soranoba/mustache.git", - {tag, "v0.3.0"}}}, - {getopt, "", - {git, "https://github.com/jcomellas/getopt.git", - {branch, "master"}}}]}. +{deps, [{erlware_commons, "0.13.0"}, + {providers, "1.4.1"}, + {getopt, "0.8.2"}, + {bbmustache, "1.0.3"}, + {relx, "3.3.0"}]}. {escript_name, rebar3}. {escript_emu_args, "%%! +sbtu +A0\n"}. @@ -26,23 +15,22 @@ {"priv/templates/*", "."}, {"rebar/include/*", "."}]}. -{erl_opts, - [{platform_define, "^[0-9]+", namespaced_types}, - no_debug_info, - warnings_as_errors]}. +{erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, + no_debug_info, + warnings_as_errors]}. {dialyzer_plt_apps, [common_test, dialyzer, eunit, snmp]}. %% Profiles -{profiles, [{test, - [{deps, [ - {meck, "", {git, "https://github.com/eproxus/meck.git", {tag, "0.8.2"}}} - ]}, - {erl_opts, [debug_info]} - ]}, +{profiles, [{test, [ + {deps, [{meck, "0.8.2"}]}, + {erl_opts, [debug_info]} + ] + }, - %% We don't want erlydtl to attempt to run on the first compile pass to bootstrap - {bootstrap, []} + {bootstrap, []}, + + {dialyze, [{erl_opts, [debug_info]}]} ]}. %% Overrides @@ -51,16 +39,23 @@ {platform_define, "^R1[4|5]", deprecated_crypto}, no_debug_info, warnings_as_errors]}, - {deps, []}, {plugins, []} + {deps, []}, {plugins, []}, + {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]} ]}, - {override, mustache, [ - {erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, - no_debug_info]}, - {deps, []}, {plugins, []}]}, - {override, getopt, [{erl_opts, [no_debug_info]}]}, - {override, providers, [{erl_opts, [no_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]}]} + warnings_as_errors]}, + {profiles, [{dialyze, [{erl_opts, [debug_info]}]}]} + ]} ]}. diff --git a/rebar.config.sample b/rebar.config.sample index f6d27c8..1bd5e0c 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -83,14 +83,14 @@ %% == Cover == -%% Whether to enable coverage reporting. Default is `false' +%% Whether to enable coverage reporting where commands support cover. Default +%% is `false' {cover_enabled, false}. -%% Whether to print coverage report to console. Default is `false' -{cover_print_enabled, false}. +%% Options to pass to cover provider +{cover_opts, [verbose]}. -%% Directory to store collected cover data -{cover_data_dir, "cover"}. +%% == Dependencies == %% What dependencies we have, dependencies can be of 3 forms, an application %% name as an atom, eg. mochiweb, a name and a version (from the .app file), or @@ -1,20 +1,5 @@ -[{<<"relx">>, - {git,"https://github.com/erlware/relx.git", - {ref,"74fec3455ba6dbb7d9e369137c1b15ab10804993"}}, - 0}, - {<<"providers">>, - {git,"https://github.com/tsloughter/providers.git", - {ref,"adc0af0a3f5de2049419a753777686b94f4e2c90"}}, - 0}, - {<<"mustache">>, - {git,"https://github.com/soranoba/mustache.git", - {ref,"e5401042c66039eef20ee81abc1537ced1f22bc7"}}, - 0}, - {<<"getopt">>, - {git,"https://github.com/jcomellas/getopt.git", - {ref,"626698975e63866156159661d100785d65eab6f9"}}, - 0}, - {<<"erlware_commons">>, - {git,"https://github.com/erlware/erlware_commons.git", - {ref,"ef0d252b11c863f9c228af2fe93a4e42fba2f7f3"}}, - 0}]. +[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.3">>},0}, + {<<"providers">>,{pkg,<<"providers">>,<<"1.4.1">>},0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.13.0">>},0}, + {<<"relx">>,{pkg,<<"relx">>,<<"3.3.0">>},0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0}]. diff --git a/src/rebar.app.src b/src/rebar.app.src index 6d081a2..e5d56bb 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "3.0.0-alpha-6"}, + {vsn, "3.0.0-beta-1"}, {modules, []}, {registered, []}, {applications, [kernel, @@ -17,6 +17,7 @@ common_test, erlware_commons, providers, + bbmustache, relx, inets]}, {env, [ @@ -47,6 +48,7 @@ rebar_prv_plugins, rebar_prv_plugins_upgrade, rebar_prv_release, + rebar_prv_relup, rebar_prv_report, rebar_prv_shell, rebar_prv_tar, diff --git a/src/rebar3.erl b/src/rebar3.erl index 5c0559f..a797c46 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -272,4 +272,7 @@ start_and_load_apps() -> application:start(asn1), application:start(public_key), application:start(ssl), - inets:start(). + inets:start(), + inets:start(httpc, [{profile, rebar}]), + rebar_utils:set_httpc_options(). + diff --git a/src/rebar_agent.erl b/src/rebar_agent.erl index 0432fb8..dc45dcf 100644 --- a/src/rebar_agent.erl +++ b/src/rebar_agent.erl @@ -20,17 +20,17 @@ do(Namespace, Command) when is_atom(Namespace), is_atom(Command) -> gen_server:call(?MODULE, {cmd, Namespace, Command}, infinity). init(State) -> - {ok, Cwd} = file:get_cwd(), + Cwd = rebar_dir:get_cwd(), {ok, #state{state=State, cwd=Cwd}}. handle_call({cmd, Command}, _From, State=#state{state=RState, cwd=Cwd}) -> MidState = maybe_show_warning(State), {Res, NewRState} = run(default, Command, RState, Cwd), - {reply, Res, MidState#state{state=NewRState}}; + {reply, Res, MidState#state{state=NewRState}, hibernate}; handle_call({cmd, Namespace, Command}, _From, State = #state{state=RState, cwd=Cwd}) -> MidState = maybe_show_warning(State), {Res, NewRState} = run(Namespace, Command, RState, Cwd), - {reply, Res, MidState#state{state=NewRState}}; + {reply, Res, MidState#state{state=NewRState}, hibernate}; handle_call(_Call, _From, State) -> {noreply, State}. @@ -48,8 +48,8 @@ terminate(_Reason, _State) -> run(Namespace, Command, RState, Cwd) -> try - case file:get_cwd() of - {ok, Cwd} -> + case rebar_dir:get_cwd() of + Cwd -> Args = [atom_to_list(Namespace), atom_to_list(Command)], CmdState0 = refresh_state(RState, Cwd), CmdState1 = rebar_state:set(CmdState0, task, atom_to_list(Command)), @@ -87,16 +87,22 @@ refresh_paths(RState) -> %% make sure to never reload self; halt()s the VM ) -- [filename:dirname(code:which(?MODULE))], %% Similar to rebar_utils:update_code/1, but also forces a reload - %% of used modules. + %% of used modules. Also forces to reload all of ebin/ instead + %% of just the modules in the .app file, because 'extra_src_dirs' + %% allows to load and compile files that are not to be kept + %% in the app file. lists:foreach(fun(Path) -> Name = filename:basename(Path, "/ebin"), + Files = filelib:wildcard(filename:join([Path, "*.beam"])), + Modules = [list_to_atom(filename:basename(F, ".beam")) + || F <- Files], App = list_to_atom(Name), application:load(App), case application:get_key(App, modules) of undefined -> code:add_patha(Path), ok; - {ok, Modules} -> + {ok, _} -> ?DEBUG("reloading ~p from ~s", [Modules, Path]), code:replace_path(Name, Path), [begin code:purge(M), code:delete(M), code:load_file(M) end @@ -108,5 +114,5 @@ refresh_state(RState, _Dir) -> lists:foldl( fun(F, State) -> F(State) end, rebar3:init_config(), - [fun(S) -> rebar_state:current_profiles(S, rebar_state:current_profiles(RState)) end] + [fun(S) -> rebar_state:apply_profiles(S, rebar_state:current_profiles(RState)) end] ). diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl index 2eb9d91..9c4a5ff 100644 --- a/src/rebar_app_discover.erl +++ b/src/rebar_app_discover.erl @@ -139,7 +139,7 @@ app_dir(AppFile) -> -spec create_app_info(file:name(), file:name()) -> rebar_app_info:t() | {error, term()}. create_app_info(AppDir, AppFile) -> - [{application, AppName, AppDetails}] = rebar_file_utils:try_consult(AppFile), + [{application, AppName, AppDetails}] = rebar_config:consult_app_file(AppFile), AppVsn = proplists:get_value(vsn, AppDetails), Applications = proplists:get_value(applications, AppDetails, []), IncludedApplications = proplists:get_value(included_applications, AppDetails, []), diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 7439cff..f097429 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -128,9 +128,17 @@ do([ProviderName | Rest], State) -> {error, Error} catch error:undef -> - %% This should really only happen if a plugin provider doesn't export do/1 - ?DEBUG("Undefined call to provider's do function:~n~p", [erlang:get_stacktrace()]), - ?PRV_ERROR({bad_provider_namespace, ProviderName}) + Stack = erlang:get_stacktrace(), + case Stack of + [{ProviderName, do, [_], _}|_] -> + %% This should really only happen if a plugin provider doesn't export do/1 + ?DEBUG("Undefined call to provider's do/1 function:~n~p", [Stack]), + ?PRV_ERROR({bad_provider_namespace, ProviderName}); + _ -> % re-raise + erlang:raise(error, undef, Stack) + end; + error:{badrecord,provider} -> + {error, ProviderName} end. format_error({bad_provider_namespace, {Namespace, Name}}) -> diff --git a/src/rebar_digraph.erl b/src/rebar_digraph.erl index d52a811..a9c1cbb 100644 --- a/src/rebar_digraph.erl +++ b/src/rebar_digraph.erl @@ -91,9 +91,9 @@ cull_deps(Graph, Vertices, Level, Levels, Solution, Discarded) -> lists:foldl(fun({Key, _}=N, {NewVertices1, SolutionAcc1, LevelsAcc1, DiscardedAcc1}) -> case dict:find(Key, SolutionAcc1) of {ok, N} -> % already seen - {NewVertices1, SolutionAcc1, LevelsAcc, DiscardedAcc1}; + {NewVertices1, SolutionAcc1, LevelsAcc1, DiscardedAcc1}; {ok, _} -> % conflict resolution! - {NewVertices1, SolutionAcc1, LevelsAcc, [N|DiscardedAcc1]}; + {NewVertices1, SolutionAcc1, LevelsAcc1, [N|DiscardedAcc1]}; error -> {[N | NewVertices1], dict:store(Key, N, SolutionAcc1), diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index e226633..7af94ea 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -100,7 +100,11 @@ local_cache_dir(Dir) -> get_cwd() -> {ok, Dir} = file:get_cwd(), - Dir. + %% On windows cwd may return capital letter for drive, + %% for example C:/foobar. But as said in http://www.erlang.org/doc/man/filename.html#join-1 + %% filename:join/1,2 anyway will convert drive-letter to lowercase, so we have to "internalize" + %% cwd as soon as it possible. + filename:join([Dir]). template_globals(State) -> filename:join([global_config_dir(State), "templates", "globals"]). diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index b9072a3..87cf352 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -404,10 +404,13 @@ target_base(OutDir, Source) -> -spec compile_mib(file:filename(), file:filename(), rebar_state:t()) -> 'ok'. compile_mib(Source, Target, Config) -> + Dir = rebar_state:dir(Config), ok = filelib:ensure_dir(Target), - ok = filelib:ensure_dir(filename:join("include", "dummy.hrl")), - Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++ + ok = filelib:ensure_dir(filename:join([Dir, "include", "dummy.hrl"])), + Opts = [{outdir, filename:join([Dir, "priv", "mibs"])} + ,{i, [filename:join([Dir, "priv", "mibs"])]}] ++ rebar_state:get(Config, mib_opts, []), + case snmpc:compile(Source, Opts) of {ok, _} -> Mib = filename:rootname(Target), diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl index ad30172..b4cdc27 100644 --- a/src/rebar_file_utils.erl +++ b/src/rebar_file_utils.erl @@ -36,10 +36,14 @@ write_file_if_contents_differ/2, system_tmpdir/0, system_tmpdir/1, - reset_dir/1]). + reset_dir/1, + touch/1]). -include("rebar.hrl"). + -include_lib("providers/include/providers.hrl"). +-include_lib("kernel/include/file.hrl"). + %% =================================================================== %% Public API @@ -71,9 +75,40 @@ symlink_or_copy(Source, Target) -> {error, eexist} -> ok; {error, _} -> - cp_r([Source], Target) + case os:type() of + {win32, _} -> + S = unicode:characters_to_list(Source), + T = unicode:characters_to_list(Target), + case filelib:is_dir(S) of + true -> + win32_symlink(S, T); + false -> + cp_r([S], T) + end; + _ -> + case filelib:is_dir(Target) of + true -> + ok; + false -> + cp_r([Source], Target) + end + end + end. + +win32_symlink(Source, Target) -> + Res = rebar_utils:sh( + ?FMT("cmd /c mklink /j \"~s\" \"~s\"", + [filename:nativename(Target), filename:nativename(Source)]), + [{use_stdout, false}, return_on_error]), + case win32_ok(Res) of + true -> ok; + false -> + {error, lists:flatten( + io_lib:format("Failed to symlink ~s to ~s~n", + [Source, Target]))} end. + %% @doc Remove files and directories. %% Target is a single filename, directoryname or wildcard expression. -spec rm_rf(string()) -> 'ok'. @@ -120,21 +155,32 @@ mv(Source, Dest) -> [{use_stdout, false}, abort_on_error]), ok; {win32, _} -> - {ok, R} = rebar_utils:sh( - ?FMT("move /y \"~s\" \"~s\" 1> nul", - [filename:nativename(Source), - filename:nativename(Dest)]), + Cmd = case filelib:is_dir(Source) of + true -> + ?FMT("robocopy /move /s \"~s\" \"~s\" 1> nul", + [filename:nativename(Source), + filename:nativename(Dest)]); + false -> + ?FMT("robocopy /move /s \"~s\" \"~s\" \"~s\" 1> nul", + [filename:nativename(filename:dirname(Source)), + filename:nativename(Dest), + filename:basename(Source)]) + end, + Res = rebar_utils:sh(Cmd, [{use_stdout, false}, return_on_error]), - case R of - [] -> - ok; - _ -> + case win32_ok(Res) of + true -> ok; + false -> {error, lists:flatten( io_lib:format("Failed to move ~s to ~s~n", [Source, Dest]))} end end. +win32_ok({ok, _}) -> true; +win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true; +win32_ok(_) -> false. + delete_each([]) -> ok; delete_each([File | Rest]) -> @@ -186,6 +232,17 @@ reset_dir(Path) -> %% recreate the directory filelib:ensure_dir(filename:join([Path, "dummy.beam"])). + +%% Linux touch but using erlang functions to work in bot *nix os and +%% windows +-spec touch(Path) -> ok | {error, Reason} when + Path :: file:name(), + Reason :: file:posix(). +touch(Path) -> + {ok, A} = file:read_file_info(Path), + ok = file:write_file_info(Path, A#file_info{mtime = calendar:local_time(), + atime = calendar:local_time()}). + %% =================================================================== %% Internal functions %% =================================================================== @@ -198,28 +255,36 @@ delete_each_dir_win32([Dir | Rest]) -> delete_each_dir_win32(Rest). xcopy_win32(Source,Dest)-> - {ok, R} = rebar_utils:sh( - ?FMT("xcopy \"~s\" \"~s\" /q /y /e 2> nul", - [filename:nativename(Source), filename:nativename(Dest)]), + %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to + %% handle long names. May have issues with older windows. + Cmd = case filelib:is_dir(Source) of + true -> + ?FMT("robocopy \"~s\" \"~s\" /e /is 1> nul", + [filename:nativename(Source), + filename:nativename(Dest)]); + false -> + ?FMT("robocopy \"~s\" \"~s\" \"~s\" /e /is 1> nul", + [filename:nativename(filename:dirname(Source)), + filename:nativename(Dest), + filename:basename(Source)]) + end, + Res = rebar_utils:sh(Cmd, [{use_stdout, false}, return_on_error]), - case length(R) > 0 of - %% when xcopy fails, stdout is empty and and error message is printed - %% to stderr (which is redirected to nul) - true -> ok; - false -> - {error, lists:flatten( - io_lib:format("Failed to xcopy from ~s to ~s~n", - [Source, Dest]))} + case win32_ok(Res) of + true -> ok; + false -> + {error, lists:flatten( + io_lib:format("Failed to copy ~s to ~s~n", + [Source, Dest]))} end. cp_r_win32({true, SourceDir}, {true, DestDir}) -> %% from directory to directory - SourceBase = filename:basename(SourceDir), - ok = case file:make_dir(filename:join(DestDir, SourceBase)) of + ok = case file:make_dir(DestDir) of {error, eexist} -> ok; Other -> Other end, - ok = xcopy_win32(SourceDir, filename:join(DestDir, SourceBase)); + ok = xcopy_win32(SourceDir, DestDir); cp_r_win32({false, Source} = S,{true, DestDir}) -> %% from file to directory cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))}); diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 2d83579..e2f3f69 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -68,6 +68,7 @@ compare_url(Dir, Url) -> CurrentUrl1 = string:strip(string:strip(CurrentUrl, both, $\n), both, $\r), ParsedUrl = parse_git_url(Url), ParsedCurrentUrl = parse_git_url(CurrentUrl1), + ?DEBUG("Comparing git url ~p with ~p", [ParsedUrl, ParsedCurrentUrl]), ParsedCurrentUrl =:= ParsedUrl. parse_git_url("git@" ++ HostPath) -> @@ -76,6 +77,9 @@ parse_git_url("git@" ++ HostPath) -> parse_git_url("git://" ++ HostPath) -> [Host | Path] = string:tokens(HostPath, "/"), {Host, filename:rootname(filename:join(Path), ".git")}; +parse_git_url("http://" ++ HostPath) -> + [Host | Path] = string:tokens(HostPath, "/"), + {Host, filename:rootname(filename:join(Path), ".git")}; parse_git_url("https://" ++ HostPath) -> [Host | Path] = string:tokens(HostPath, "/"), {Host, filename:rootname(filename:join(Path), ".git")}. @@ -109,28 +113,23 @@ download(Dir, {git, Url, Rev}, _State) -> rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, Dir}]). make_vsn(Dir) -> - {ok, Cwd} = file:get_cwd(), - try - ok = file:set_cwd(Dir), - {Vsn, RawRef, RawCount} = collect_default_refcount(), - {plain, build_vsn_string(Vsn, RawRef, RawCount)} - after - file:set_cwd(Cwd) - end. + {Vsn, RawRef, RawCount} = collect_default_refcount(Dir), + {plain, build_vsn_string(Vsn, RawRef, RawCount)}. %% Internal functions -collect_default_refcount() -> +collect_default_refcount(Dir) -> %% Get the tag timestamp and minimal ref from the system. The %% timestamp is really important from an ordering perspective. AbortMsg1 = "Getting log of git dependency failed in " ++ rebar_dir:get_cwd(), {ok, String} = rebar_utils:sh("git log -n 1 --pretty=format:\"%h\n\" ", [{use_stdout, false}, + {cd, Dir}, {debug_abort_on_error, AbortMsg1}]), RawRef = string:strip(String, both, $\n), - {Tag, TagVsn} = parse_tags(), + {Tag, TagVsn} = parse_tags(Dir), {ok, RawCount} = case Tag of undefined -> @@ -140,7 +139,7 @@ collect_default_refcount() -> {debug_abort_on_error, AbortMsg2}]), rebar_utils:line_count(PatchLines); _ -> - get_patch_count(Tag) + get_patch_count(Dir, Tag) end, {TagVsn, RawRef, RawCount}. @@ -158,8 +157,8 @@ build_vsn_string(Vsn, RawRef, Count) -> integer_to_list(Count), RefTag])) end. -get_patch_count(RawRef) -> - AbortMsg = "Getting rev-list of git dep failed in " ++ rebar_dir:get_cwd(), +get_patch_count(Dir, RawRef) -> + AbortMsg = "Getting rev-list of git dep failed in " ++ Dir, Ref = re:replace(RawRef, "\\s", "", [global]), Cmd = io_lib:format("git rev-list ~s..HEAD", [Ref]), @@ -169,10 +168,10 @@ get_patch_count(RawRef) -> rebar_utils:line_count(PatchLines). -parse_tags() -> +parse_tags(Dir) -> %% Don't abort on error, we want the bad return to be turned into 0.0.0 case rebar_utils:sh("git log --oneline --no-walk --tags --decorate", - [{use_stdout, false}, return_on_error]) of + [{use_stdout, false}, return_on_error, {cd, Dir}]) of {error, _} -> {undefined, "0.0.0"}; {ok, Line} -> diff --git a/src/rebar_hooks.erl b/src/rebar_hooks.erl index 9518542..857336c 100644 --- a/src/rebar_hooks.erl +++ b/src/rebar_hooks.erl @@ -1,6 +1,10 @@ -module(rebar_hooks). --export([run_all_hooks/5]). +-export([run_all_hooks/5 + ,format_error/1]). + +-include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). -spec run_all_hooks(file:filename_all(), pre | post, atom() | {atom(), atom()} | string(), @@ -10,21 +14,39 @@ run_all_hooks(Dir, Type, Command, Providers, State) -> run_hooks(Dir, Type, Command, State). run_provider_hooks(Dir, Type, Command, Providers, State) -> + case rebar_state:get(State, provider_hooks, []) of + [] -> + ok; + AllHooks -> + TypeHooks = proplists:get_value(Type, AllHooks, []), + run_provider_hooks(Dir, Type, Command, Providers, TypeHooks, State) + end. + +run_provider_hooks(_Dir, _Type, _Command, _Providers, [], _State) -> + ok; +run_provider_hooks(Dir, Type, Command, Providers, TypeHooks, State) -> PluginDepsPaths = rebar_state:code_paths(State, all_plugin_deps), code:add_pathsa(PluginDepsPaths), Providers1 = rebar_state:providers(State), State1 = rebar_state:providers(rebar_state:dir(State, Dir), Providers++Providers1), - AllHooks = rebar_state:get(State1, provider_hooks, []), - TypeHooks = proplists:get_value(Type, AllHooks, []), HookProviders = proplists:get_all_values(Command, TypeHooks), - State2 = rebar_core:do(HookProviders, State1), - rebar_utils:remove_from_code_path(PluginDepsPaths), - State2. + case rebar_core:do(HookProviders, State1) of + {error, ProviderName} -> + ?DEBUG(format_error({bad_provider, Type, Command, ProviderName}), []), + throw(?PRV_ERROR({bad_provider, Type, Command, ProviderName})); + {ok, _} -> + rebar_utils:remove_from_code_path(PluginDepsPaths) + end. + +format_error({bad_provider, Type, Command, {Name, Namespace}}) -> + io_lib:format("Unable to run ~s hooks for '~p', command '~p' in namespace '~p' not found.", [Type, Command, Namespace, Name]); +format_error({bad_provider, Type, Command, Name}) -> + io_lib:format("Unable to run ~s hooks for '~p', command '~p' not found.", [Type, Command, Name]). %% @doc The following environment variables are exported when running %% a hook (absolute paths): -%% +%% %% REBAR_DEPS_DIR = rebar_dir:deps_dir/1 %% REBAR_BUILD_DIR = rebar_dir:base_dir/1 %% REBAR_ROOT_DIR = rebar_dir:root_dir/1 @@ -36,7 +58,7 @@ run_provider_hooks(Dir, Type, Command, Providers, State) -> %% REBAR_APP_DIRS = rebar_dir:lib_dirs/1 %% REBAR_SRC_DIRS = rebar_dir:src_dirs/1 %% -%% autoconf compatible variables +%% autoconf compatible variables %% (see: http://www.gnu.org/software/autoconf/manual/autoconf.html#Erlang-Libraries): %% ERLANG_ERTS_VER = erlang:system_info(version) %% ERLANG_ROOT_DIR = code:root_dir/0 @@ -45,23 +67,24 @@ run_provider_hooks(Dir, Type, Command, Providers, State) -> %% ERL = ERLANG_ROOT_DIR/bin/erl %% ERLC = ERLANG_ROOT_DIR/bin/erl %% +run_hooks(Dir, pre, Command, State) -> + run_hooks(Dir, pre_hooks, Command, State); +run_hooks(Dir, post, Command, State) -> + run_hooks(Dir, post_hooks, Command, State); run_hooks(Dir, Type, Command, State) -> - Hooks = case Type of - pre -> - rebar_state:get(State, pre_hooks, []); - post -> - rebar_state:get(State, post_hooks, []); - _ -> - [] - end, - Env = create_env(State), - lists:foreach(fun({_, C, _}=Hook) when C =:= Command -> - apply_hook(Dir, Env, Hook); - ({C, _}=Hook) when C =:= Command -> - apply_hook(Dir, Env, Hook); - (_) -> - continue - end, Hooks). + case rebar_state:get(State, Type, []) of + [] -> + ok; + Hooks -> + Env = create_env(State), + lists:foreach(fun({_, C, _}=Hook) when C =:= Command -> + apply_hook(Dir, Env, Hook); + ({C, _}=Hook) when C =:= Command -> + apply_hook(Dir, Env, Hook); + (_) -> + continue + end, Hooks) + end. apply_hook(Dir, Env, {Arch, Command, Hook}) -> case rebar_utils:is_arch(Arch) of @@ -100,8 +123,6 @@ join_dirs(BaseDir, Dirs) -> re_version(Path) -> case re:run(Path, "^.*-(?<VER>[^/-]*)$", [{capture, [1], list}]) of - nomatch -> ""; - {match, [Ver]} -> Ver + nomatch -> ""; + {match, [Ver]} -> Ver end. - - diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 59ce0dc..450ff1e 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -95,7 +95,8 @@ make_vsn(_) -> request(Url, ETag) -> case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]}, [{relaxed, true}], - [{body_format, binary}]) of + [{body_format, binary}], + rebar) of {ok, {{_Version, 200, _Reason}, Headers, Body}} -> ?DEBUG("Successfully downloaded ~s", [Url]), {"etag", ETag1} = lists:keyfind("etag", 1, Headers), diff --git a/src/rebar_plugins.erl b/src/rebar_plugins.erl index a267b9d..3e855de 100644 --- a/src/rebar_plugins.erl +++ b/src/rebar_plugins.erl @@ -21,14 +21,15 @@ project_apps_install(State) -> lists:foldl(fun(Profile, StateAcc) -> Plugins = rebar_state:get(State, {plugins, Profile}, []), - handle_plugins(Profile, Plugins, StateAcc), - lists:foldl(fun(App, StateAcc1) -> + StateAcc1 = handle_plugins(Profile, Plugins, StateAcc), + + lists:foldl(fun(App, StateAcc2) -> AppDir = rebar_app_info:dir(App), C = rebar_config:consult(AppDir), S = rebar_state:new(rebar_state:new(), C, AppDir), Plugins2 = rebar_state:get(S, {plugins, Profile}, []), - handle_plugins(Profile, Plugins2, StateAcc1) - end, StateAcc, ProjectApps) + handle_plugins(Profile, Plugins2, StateAcc2) + end, StateAcc1, ProjectApps) end, State, Profiles). -spec install(rebar_state:t()) -> rebar_state:t(). @@ -49,21 +50,20 @@ handle_plugins(Profile, Plugins, State, Upgrade) -> State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR), %% Install each plugin individually so if one fails to install it doesn't effect the others - {PluginProviders, State2} = + {_PluginProviders, State2} = lists:foldl(fun(Plugin, {PluginAcc, StateAcc}) -> {NewPlugins, NewState} = handle_plugin(Profile, Plugin, StateAcc, Upgrade), - {PluginAcc++NewPlugins, NewState} + NewState1 = rebar_state:create_logic_providers(NewPlugins, NewState), + {PluginAcc++NewPlugins, NewState1} end, {[], State1}, Plugins), %% reset deps dir State3 = rebar_state:set(State2, deps_dir, DepsDir), - State4 = rebar_state:lock(State3, Locks), - - rebar_state:create_logic_providers(PluginProviders, State4). + rebar_state:lock(State3, Locks). handle_plugin(Profile, Plugin, State, Upgrade) -> try - {ok, Apps, State2} = rebar_prv_install_deps:handle_deps(Profile, State, [Plugin], Upgrade), + {Apps, State2} = rebar_prv_install_deps:handle_deps_as_profile(Profile, State, [Plugin], Upgrade), {no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(Apps), ToBuild = rebar_prv_install_deps:cull_compile(Sorted, []), @@ -72,7 +72,7 @@ handle_plugin(Profile, Plugin, State, Upgrade) -> code:add_pathsa(CodePaths), %% Build plugin and its deps - [build_plugin(AppInfo, Apps, State) || AppInfo <- ToBuild], + [build_plugin(AppInfo, Apps, State2) || AppInfo <- ToBuild], %% Add newly built deps and plugin to code path State3 = rebar_state:update_all_plugin_deps(State2, Apps), @@ -92,10 +92,9 @@ handle_plugin(Profile, Plugin, State, Upgrade) -> build_plugin(AppInfo, Apps, State) -> Providers = rebar_state:providers(State), - AppDir = rebar_app_info:dir(AppInfo), - C = rebar_config:consult(AppDir), - S = rebar_state:new(rebar_state:all_deps(rebar_state:new(), Apps), C, AppDir), - rebar_prv_compile:compile(S, Providers, AppInfo). + Providers1 = rebar_state:providers(rebar_app_info:state(AppInfo)), + S = rebar_state:all_deps(rebar_app_info:state_or_new(State, AppInfo), Apps), + rebar_prv_compile:compile(S, Providers++Providers1, AppInfo). plugin_providers({Plugin, _, _, _}) when is_atom(Plugin) -> validate_plugin(Plugin); diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 710922a..2b024cf 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -105,14 +105,18 @@ run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)). run_test_quiet(Opts) -> Pid = self(), + Ref = erlang:make_ref(), LogDir = proplists:get_value(logdir, Opts), - erlang:spawn_monitor(fun() -> + {_, Monitor} = erlang:spawn_monitor(fun() -> {ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]), [write]), true = group_leader(F, self()), - Pid ! ct:run_test(Opts) + Pid ! {Ref, ct:run_test(Opts)} end), - receive Result -> handle_quiet_results(Opts, Result) end. + receive + {Ref, Result} -> handle_quiet_results(Opts, Result); + {'DOWN', Monitor, _, _, Reason} -> handle_results(?PRV_ERROR(Reason)) + end. handle_results(Results) when is_list(Results) -> Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results), @@ -132,8 +136,6 @@ sum_results({Passed, Failed, {UserSkipped, AutoSkipped}}, handle_quiet_results(_, {error, _} = Result) -> handle_results(Result); -handle_quiet_results(_, {'DOWN', _, _, _, Reason}) -> - handle_results(?PRV_ERROR(Reason)); handle_quiet_results(CTOpts, Results) when is_list(Results) -> _ = [format_result(Result) || Result <- Results], case handle_results(Results) of @@ -345,23 +347,26 @@ reduce_path([_|Acc], [".."|Rest]) -> reduce_path(Acc, Rest); reduce_path([], [".."|Rest]) -> reduce_path([], Rest); reduce_path(Acc, [Component|Rest]) -> reduce_path([Component|Acc], Rest). -remove_links(Path) -> - case ec_file:is_dir(Path) of - false -> ok; - true -> remove_links1(Path) - end. -remove_links1(Path) -> +remove_links(Path) -> + IsDir = ec_file:is_dir(Path), case ec_file:is_symlink(Path) of - true -> - file:delete(Path); - false -> - lists:foreach(fun(ChildPath) -> - remove_links(ChildPath) - end, sub_dirs(Path)) + true when IsDir -> + delete_dir_link(Path); + false when IsDir -> + lists:foreach(fun(ChildPath) -> + remove_links(ChildPath) + end, dir_entries(Path)); + _ -> file:delete(Path) end. -sub_dirs(Path) -> +delete_dir_link(Path) -> + case os:type() of + {unix, _} -> file:delete(Path); + {win32, _} -> file:del_dir(Path) + end. + +dir_entries(Path) -> {ok, SubDirs} = file:list_dir(Path), [filename:join(Path, SubDir) || SubDir <- SubDirs]. diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl index 6eb8a4f..25d5193 100644 --- a/src/rebar_prv_compile.erl +++ b/src/rebar_prv_compile.erl @@ -126,7 +126,20 @@ copy_app_dirs(State, OldAppDir, AppDir) -> false -> ok end, + filelib:ensure_dir(filename:join(AppDir, "dummy")), + + %% link or copy mibs if it exists + case filelib:is_dir(filename:join(OldAppDir, "mibs")) of + true -> + %% If mibs exist it means we must ensure priv exists. + %% mibs files are compiled to priv/mibs/ + filelib:ensure_dir(filename:join([OldAppDir, "priv", "dummy"])), + symlink_or_copy(OldAppDir, AppDir, "mibs"); + false -> + ok + end, + %% link to src_dirs to be adjacent to ebin is needed for R15 use of cover/xref SrcDirs = rebar_dir:all_src_dirs(State, ["src"], ["test"]), [symlink_or_copy(OldAppDir, AppDir, Dir) || Dir <- ["priv", "include"] ++ SrcDirs]; diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index 6c115b6..8c26521 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -337,16 +337,24 @@ write_coverdata(State, Task) -> ?WARN("Cover data export failed: ~p", [Reason]) end. -verbose(State) -> +command_line_opts(State) -> {Opts, _} = rebar_state:command_parsed_args(State), - case proplists:get_value(verbose, Opts, missing) of - missing -> rebar_state:get(State, cover_print_enabled, false); - Else -> Else + Opts. + +config_opts(State) -> + rebar_state:get(State, cover_opts, []). + +verbose(State) -> + Command = proplists:get_value(verbose, command_line_opts(State), undefined), + Config = proplists:get_value(verbose, config_opts(State), undefined), + case {Command, Config} of + {undefined, undefined} -> false; + {undefined, Verbose} -> Verbose; + {Verbose, _} -> Verbose end. cover_dir(State) -> - rebar_state:get(State, cover_data_dir, filename:join([rebar_dir:base_dir(State), - "cover"])). + filename:join([rebar_dir:base_dir(State), "cover"]). cover_opts(_State) -> [{reset, $r, "reset", boolean, help(reset)}, diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 15f1dac..1cf7b71 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -72,6 +72,7 @@ short_desc() -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> + maybe_fix_env(), ?INFO("Dialyzer starting, this may take a while...", []), code:add_pathsa(rebar_state:code_paths(State, all_deps)), Plt = get_plt(State), @@ -91,6 +92,13 @@ do(State) -> rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)) end. +%% This is used to workaround dialyzer quirk discussed here +%% https://github.com/rebar/rebar3/pull/489#issuecomment-107953541 +%% Dialyzer gets default plt location wrong way by peeking HOME environment +%% variable which usually is not defined on Windows. +maybe_fix_env() -> + os:putenv("DIALYZER_PLT", filename:join(rebar_dir:home_dir(), ".dialyzer_plt")). + -spec format_error(any()) -> iolist(). format_error({error_processing_apps, Error}) -> io_lib:format("Error in dialyzing apps: ~s", [Error]); @@ -380,7 +388,7 @@ run_dialyzer(State, Opts, Output) -> {check_plt, false} | Opts], ?DEBUG("Running dialyzer with options: ~p~n", [Opts2]), - _ = dialyzer:run(Opts2), + dialyzer:run(Opts2), {0, State} end. diff --git a/src/rebar_prv_do.erl b/src/rebar_prv_do.erl index 35e85c2..f850135 100644 --- a/src/rebar_prv_do.erl +++ b/src/rebar_prv_do.erl @@ -33,8 +33,16 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Tasks = rebar_utils:args_to_tasks(rebar_state:command_args(State)), - do_tasks(Tasks, State). + case rebar_utils:args_to_tasks(rebar_state:command_args(State)) of + [] -> + AllProviders = rebar_state:providers(State), + Namespace = rebar_state:namespace(State), + Providers = providers:get_providers_by_namespace(Namespace, AllProviders), + providers:help(Providers), + {ok, State}; + Tasks -> + do_tasks(Tasks, State) + end. do_tasks([], State) -> {ok, State}; diff --git a/src/rebar_prv_help.erl b/src/rebar_prv_help.erl index 4bc259a..c028264 100644 --- a/src/rebar_prv_help.erl +++ b/src/rebar_prv_help.erl @@ -65,7 +65,13 @@ task_help(Namespace, Name, State) -> Providers = rebar_state:providers(State), case providers:get_provider(Name, Providers, Namespace) of not_found -> - {error, io_lib:format("Unknown task ~p", [Name])}; + case providers:get_providers_by_namespace(Name, Providers) of + [] -> + {error, io_lib:format("Unknown task ~p", [Name])}; + NSProviders -> + providers:help(NSProviders), + {ok, State} + end; Provider -> providers:help(Provider), {ok, State} diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 806293b..105bbb1 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -35,10 +35,7 @@ -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). --export([handle_deps/3, - handle_deps/4, - handle_deps/5, - +-export([handle_deps_as_profile/4, find_cycles/1, cull_compile/2]). @@ -76,8 +73,8 @@ do(State) -> Profiles = rebar_state:current_profiles(State), ProjectApps = rebar_state:project_apps(State), - {Apps, State1} = - lists:foldl(fun deps_per_profile/2, {[], State}, lists:reverse(Profiles)), + Upgrade = rebar_state:get(State, upgrade, false), + {Apps, State1} = deps_per_profile(Profiles, Upgrade, State), State2 = rebar_state:update_all_deps(State1, Apps), CodePaths = [rebar_app_info:ebin_dir(A) || A <- Apps], @@ -119,63 +116,77 @@ format_error({cycles, Cycles}) -> format_error(Reason) -> io_lib:format("~p", [Reason]). --spec handle_deps(atom(), rebar_state:t(), list()) -> - {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}. -handle_deps(Profile, State, Deps) -> - handle_deps(Profile, State, Deps, false, []). - --spec handle_deps(atom(), rebar_state:t(), list(), list() | boolean()) -> - {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}. -handle_deps(Profile, State, Deps, Upgrade) when is_boolean(Upgrade) -> - handle_deps(Profile, State, Deps, Upgrade, []); -handle_deps(Profile, State, Deps, Locks) when is_list(Locks) -> - Upgrade = rebar_state:get(State, upgrade, false), - handle_deps(Profile, State, Deps, Upgrade, Locks). - --spec handle_deps(atom(), rebar_state:t(), list(), boolean() | {true, binary(), integer()}, list()) - -> {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}. -handle_deps(_Profile, State, [], _, _) -> - {ok, [], State}; -handle_deps(Profile, State0, Deps, Upgrade, Locks) -> - %% Split source deps from pkg deps, needed to keep backwards compatibility - DepsDir = profile_dep_dir(State0, Profile), - - {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State0, Locks, 0), - - %% Fetch transitive src deps - {State1, SrcApps, PkgDeps1, Seen} = update_src_deps(Profile, 0, SrcDeps, PkgDeps, [] - ,State0, Upgrade, sets:new(), Locks), - - {Solved, State4} = - case PkgDeps1 of - [] -> - {[], State1}; - _ -> - %% Read in package index and dep graph - {Packages, Graph} = rebar_state:packages(State1), - Registry = rebar_packages:registry(State1), - State2 = rebar_state:packages(rebar_state:registry(State1, Registry) - ,{Packages, Graph}), - - update_pkg_deps(Profile, Packages, PkgDeps1 - ,Graph, Upgrade, Seen, State2, Locks) - end, - - AllDeps = lists:ukeymerge(2 - ,lists:ukeysort(2, SrcApps) - ,lists:ukeysort(2, Solved)), - - {ok, AllDeps, State4}. +%% Allows other providers to install deps in a given profile +%% manually, outside of what is provided by rebar3's deps tuple. +handle_deps_as_profile(Profile, State, Deps, Upgrade) -> + Locks = [], + Level = 0, + DepsDir = profile_dep_dir(State, Profile), + + {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State, Locks, Level), + AllSrcProfileDeps = [{Profile, SrcDeps, Locks, Level}], + AllPkgProfileDeps = [{Profile, Locks, PkgDeps}], + {AllApps, PkgDeps1, Seen, State1} = handle_profile_level(AllSrcProfileDeps, AllPkgProfileDeps, Locks, sets:new(), Upgrade, State), + + handle_profile_pkg_level(PkgDeps1, AllApps, Seen, Upgrade, State1). + %% =================================================================== %% Internal functions %% =================================================================== -deps_per_profile(Profile, {Apps, State}) -> +%% finds all the deps in `{deps, ...}` for each profile provided. +deps_per_profile(Profiles, Upgrade, State) -> + Level = 0, + {AllProfileDeps, PkgDeps} = lists:foldl(fun(Profile, {SrcAcc, PkgAcc}) -> + {Src, Pkg} = parse_profile_deps(State, Profile, Level), + {[Src | SrcAcc], [Pkg | PkgAcc]} + end, {[], []}, Profiles), + {AllApps, PkgDeps1, Seen, State1} = handle_profile_level(AllProfileDeps, PkgDeps, [], sets:new(), Upgrade, State), + + handle_profile_pkg_level(PkgDeps1, AllApps, Seen, Upgrade, State1). + +parse_profile_deps(State, Profile, Level) -> + DepsDir = profile_dep_dir(State, Profile), Locks = rebar_state:get(State, {locks, Profile}, []), - ProfileDeps = rebar_state:get(State, {deps, Profile}, []), - {ok, NewApps, NewState} = handle_deps(Profile, State, ProfileDeps, Locks), - {NewApps++Apps, NewState}. + Deps = rebar_state:get(State, {deps, Profile}, []), + {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State, Locks, Level), + {{Profile, SrcDeps, Locks, Level}, {Profile, Locks, PkgDeps}}. + +%% Level-order traversal of all dependencies, across profiles. +%% If profiles x,y,z are present, then the traversal will go: +%% x0, y0, z0, x1, y1, z1, ..., xN, yN, zN. +handle_profile_level([], PkgDeps, SrcApps, Seen, _Upgrade, State) -> + {SrcApps, PkgDeps, Seen, State}; +handle_profile_level([{Profile, SrcDeps, Locks, Level} | Rest], PkgDeps, SrcApps, Seen, Upgrade, State) -> + {SrcDeps1, PkgDeps1, SrcApps1, State1, Seen1, Locks1} = + update_src_deps(Profile, Level, SrcDeps, [], SrcApps + ,State, Upgrade, Seen, Locks), + SrcDeps2 = case SrcDeps1 of + [] -> Rest; + _ -> Rest ++ [{Profile, SrcDeps1, Locks1, Level+1}] + end, + handle_profile_level(SrcDeps2, [{Profile, Locks1, PkgDeps1} | PkgDeps], SrcApps1++SrcApps, sets:union(Seen, Seen1), Upgrade, State1). + +handle_profile_pkg_level(PkgDeps, AllApps, Seen, Upgrade, State) -> + %% Read in package index and dep graph + {Packages, Graph} = rebar_state:packages(State), + Registry = rebar_packages:registry(State), + State1 = rebar_state:packages(rebar_state:registry(State, Registry) + ,{Packages, Graph}), + + lists:foldl(fun({_Profile, _, []}, {AllAcc, StateAcc}) -> + {AllAcc, StateAcc}; + ({Profile1, Locks, PkgDeps2}, {AllAcc, StateAcc}) -> + {Solved, StateAcc2} = update_pkg_deps(Profile1, Packages, PkgDeps2 + ,Graph, Upgrade, Seen, StateAcc, Locks), + + AllDeps = lists:ukeymerge(2 + ,lists:ukeysort(2, AllAcc) + ,lists:ukeysort(2, Solved)), + + {AllDeps, StateAcc2} + end, {AllApps, State1}, PkgDeps). find_cycles(Apps) -> case rebar_digraph:compile_order(Apps) of @@ -224,28 +235,13 @@ update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, Locks) -> handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, Locks, State) -> IsLock = pkg_locked(Pkg, Locks), AppInfo = package_to_app(DepsDir, Packages, Pkg, IsLock, State), + Deps = rebar_app_info:deps(AppInfo), Level = rebar_app_info:dep_level(AppInfo), {NewSeen, NewState} = maybe_lock(Profile, AppInfo, Seen, State, Level), {_, AppInfo1} = maybe_fetch(AppInfo, Profile, Upgrade, Seen, NewState), - - Profiles = rebar_state:current_profiles(State), - Name = rebar_app_info:name(AppInfo1), - C = rebar_config:consult(rebar_app_info:dir(AppInfo1)), - BaseDir = rebar_state:get(State, base_dir, []), - S1 = rebar_state:new(rebar_state:set(rebar_state:new(), base_dir, BaseDir), - C, rebar_app_info:dir(AppInfo1)), - S2 = rebar_state:apply_overrides(S1, Name), - - Plugins = rebar_state:get(S2, plugins, []), - S3 = rebar_state:set(S2, {plugins, Profile}, Plugins), - - S4 = rebar_state:apply_profiles(S3, Profiles), - AppInfo2 = rebar_app_info:state(AppInfo1, S4), - - %% Dep may have plugins to install. Find and install here. - S5 = rebar_plugins:install(S4), - AppInfo3 = rebar_app_info:state(AppInfo2, S5), - + {AppInfo2, _, _, _, _} = + handle_dep(NewState, Profile, DepsDir, AppInfo1, Locks, Level), + AppInfo3 = rebar_app_info:deps(AppInfo2, Deps), {[AppInfo3 | Fetched], NewSeen, NewState}. maybe_lock(Profile, AppInfo, Seen, State, Level) -> @@ -284,29 +280,25 @@ package_to_app(DepsDir, Packages, {Name, Vsn, Level}, IsLock, State) -> throw(?PRV_ERROR({missing_package, Name, Vsn})) end; {ok, PkgDeps} -> - {ok, AppInfo} = rebar_app_info:new(Name, Vsn), - AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps), - AppInfo2 = rebar_app_info:dir(AppInfo1, filename:join([DepsDir, Name])), - AppInfo3 = rebar_app_info:dep_level(AppInfo2, Level), - AppInfo4 = rebar_app_info:is_lock(AppInfo3, IsLock), - rebar_app_info:source(AppInfo4, {pkg, Name, Vsn}) + Source = {pkg, Name, Vsn}, + AppInfo = new_dep(DepsDir, Name, Vsn, Source, IsLock, State), + AppInfo1 = rebar_app_info:dep_level(rebar_app_info:deps(AppInfo, PkgDeps), Level), + BaseDir = rebar_state:get(State, base_dir, []), + AppState1 = rebar_state:set(rebar_app_info:state(AppInfo1), base_dir, BaseDir), + rebar_app_info:state(AppInfo1, AppState1) end. -spec update_src_deps(atom(), non_neg_integer(), list(), list(), list(), rebar_state:t(), boolean(), sets:set(binary()), list()) -> {rebar_state:t(), list(), list(), sets:set(binary())}. update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, Locks) -> - case lists:foldl( - fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc}) -> - update_src_dep(AppInfo, Profile, Level, - SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, - Upgrade, SeenAcc, Locks, LocksAcc) - end, - {[], PkgDeps, SrcApps, State, Seen, Locks}, - rebar_utils:sort_deps(SrcDeps)) of - {[], NewPkgDeps, NewSrcApps, State1, Seen1, _NewLocks} -> - {State1, NewSrcApps, NewPkgDeps, Seen1}; - {NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Seen1, NewLocks} -> - update_src_deps(Profile, Level+1, NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Upgrade, Seen1, NewLocks) - end. + lists:foldl( + fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc}) -> + update_src_dep(AppInfo, Profile, Level, + SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, + Upgrade, SeenAcc, Locks, LocksAcc) + end, + {[], PkgDeps, SrcApps, State, Seen, Locks}, + rebar_utils:sort_deps(SrcDeps)). + update_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks) -> %% If not seen, add to list of locks to write out @@ -329,7 +321,7 @@ profile_dep_dir(State, Profile) -> _ -> rebar_dir:deps_dir(State) end. -update_seen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks) -> +update_seen_src_dep(AppInfo, _Profile, _Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks) -> Name = rebar_app_info:name(AppInfo), %% If seen from lock file or user requested an upgrade %% don't print warning about skipping @@ -338,44 +330,32 @@ update_seen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, U false when not Upgrade -> warn_skip_deps(AppInfo, State); true -> ok end, - %% scan for app children here if upgrading - case Upgrade of - false -> - {SrcDeps, PkgDeps, SrcApps, State, Seen, Locks}; - true -> - {NewSrcDeps, NewPkgDeps, NewSrcApps, NewState, NewLocks} - = handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, - Level, State, Locks), - {NewSrcDeps, NewPkgDeps, NewSrcApps, NewState, Seen, NewLocks} - end. + {SrcDeps, PkgDeps, SrcApps, State, Seen, Locks}. update_unseen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, Locks) -> {NewSeen, State1} = maybe_lock(Profile, AppInfo, Seen, State, Level), {NewSrcDeps, NewPkgDeps, NewSrcApps, State2, NewLocks} = case Upgrade of - true -> - handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, - Level, State1, Locks); - _ -> - {_, AppInfo1} = maybe_fetch(AppInfo, Profile, false, Seen, State1), - handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, - Level, State1, Locks) - end, + true -> + handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, + Level, State1, Seen, Locks); + _ -> + {_, AppInfo1} = maybe_fetch(AppInfo, Profile, false, Seen, State1), + handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, + Level, State1, Locks) + end, {NewSrcDeps, NewPkgDeps, NewSrcApps, State2, NewSeen, NewLocks}. -handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> +handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Seen, Locks) -> Name = rebar_app_info:name(AppInfo), case lists:keyfind(Name, 1, Locks) of false -> - case maybe_fetch(AppInfo, Profile, true, sets:new(), State) of - {true, AppInfo1} -> - handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, - Level, State, Locks); - {false, AppInfo1} -> - {[AppInfo1|SrcDeps], PkgDeps, SrcApps, State, Locks} - end; + {_, AppInfo1} = maybe_fetch(AppInfo, Profile, true, Seen, State), + handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, + Level, State, Locks); _StillLocked -> - {[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks} + handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, + Level, State, Locks) end. handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> @@ -401,10 +381,9 @@ handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) -> S1 = rebar_state:new(S, C, rebar_app_info:dir(AppInfo)), S2 = rebar_state:apply_overrides(S1, Name), - Plugins = rebar_state:get(S2, plugins, []), - S3 = rebar_state:set(S2, {plugins, Profile}, Plugins), - - S4 = rebar_state:apply_profiles(S3, Profiles), + S3 = rebar_state:apply_profiles(S2, Profiles), + Plugins = rebar_state:get(S3, plugins, []), + S4 = rebar_state:set(S3, {plugins, Profile}, Plugins), AppInfo1 = rebar_app_info:state(AppInfo, S4), %% Dep may have plugins to install. Find and install here. @@ -430,41 +409,27 @@ maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) -> false -> case rebar_app_discover:find_app(AppDir, all) of false -> - case already_in_default(AppInfo, State) of - false -> - case fetch_app(AppInfo, AppDir, State) of - true -> - maybe_symlink_default(State, Profile, AppDir, AppInfo), - {true, update_app_info(AppDir, AppInfo)}; - Other -> - {Other, AppInfo} - end; - {true, FoundApp} -> - %% Preserve the state we created with overrides - AppState = rebar_app_info:state(AppInfo), - FoundApp1 = rebar_app_info:state(FoundApp, AppState), - symlink_dep(rebar_app_info:dir(FoundApp1), AppDir), - {true, FoundApp1} + case fetch_app(AppInfo, AppDir, State) of + true -> + maybe_symlink_default(State, Profile, AppDir, AppInfo), + {true, update_app_info(AppDir, AppInfo)}; + Other -> + {Other, AppInfo} end; {true, AppInfo1} -> %% Preserve the state we created with overrides AppState = rebar_app_info:state(AppInfo), AppInfo2 = rebar_app_info:state(AppInfo1, AppState), - maybe_symlink_default(State, Profile, AppDir, AppInfo2), case sets:is_element(rebar_app_info:name(AppInfo), Seen) of true -> {false, AppInfo2}; false -> + maybe_symlink_default(State, Profile, AppDir, AppInfo2), {maybe_upgrade(AppInfo, AppDir, Upgrade, State), AppInfo2} end end end. -already_in_default(AppInfo, State) -> - Name = ec_cnv:to_list(rebar_app_info:name(AppInfo)), - DefaultAppDir = filename:join([rebar_state:get(State, base_dir, []), "default", "lib", Name]), - rebar_app_discover:find_app(DefaultAppDir, all). - needs_symlinking(State, Profile) -> case {rebar_state:current_profiles(State), Profile} of {[default], default} -> @@ -520,6 +485,7 @@ parse_deps(DepsDir, Deps, State, Locks, Level) -> end, {[], []}, Deps). parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_list(Vsn) -> + %% Versioned Package dependency CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), case rebar_app_info:discover(CheckoutsDir) of {ok, _App} -> @@ -530,6 +496,7 @@ parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is ,ec_cnv:to_binary(Vsn)) | PkgDepsAcc]} end; parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_atom(Name) -> + %% Unversioned package dependency {PkgName, PkgVsn} = get_package(ec_cnv:to_binary(Name), State), CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)), case rebar_app_info:discover(CheckoutsDir) of diff --git a/src/rebar_prv_packages.erl b/src/rebar_prv_packages.erl index 43e3080..8ba66de 100644 --- a/src/rebar_prv_packages.erl +++ b/src/rebar_prv_packages.erl @@ -27,9 +27,9 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - case rebar_packages:registry(State) of - {ok, Registry} -> - print_packages(Registry), + case rebar_packages:get_packages(State) of + {Dict, _} -> + print_packages(Dict), {ok, State}; error -> ?PRV_ERROR(load_registry_fail) @@ -39,13 +39,16 @@ do(State) -> format_error(load_registry_fail) -> "Failed to load package regsitry. Try running 'rebar3 update' to fix". -print_packages(Table) -> - MS = ets:fun2ms(fun({Key, [Value]}) when is_binary(Key) -> {Key, Value} end), - Pkgs = ets:select(Table, MS), - lists:foreach(fun({Name, Vsns}) -> - VsnStr = join(Vsns, <<", ">>), - io:format("~s:~n Versions: ~s~n~n", [Name, VsnStr]) - end, Pkgs). +print_packages(Dict) -> + Pkgs = lists:keysort(1, dict:fetch_keys(Dict)), + SortedPkgs = lists:foldl(fun({Pkg, Vsn}, Acc) -> + orddict:append_list(Pkg, [Vsn], Acc) + end, orddict:new(), Pkgs), + + orddict:map(fun(Name, Vsns) -> + VsnStr = join(Vsns, <<", ">>), + io:format("~s:~n Versions: ~s~n~n", [Name, VsnStr]) + end, SortedPkgs). -spec join([binary()], binary()) -> binary(). join([Bin], _Sep) -> diff --git a/src/rebar_prv_plugins_upgrade.erl b/src/rebar_prv_plugins_upgrade.erl index fbd8365..02c185f 100644 --- a/src/rebar_prv_plugins_upgrade.erl +++ b/src/rebar_prv_plugins_upgrade.erl @@ -33,10 +33,16 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> {Args, _} = rebar_state:command_parsed_args(State), - Plugin = proplists:get_value(plugin, Args, <<"">>), - upgrade(Plugin, State). + case proplists:get_value(plugin, Args, none) of + none -> + ?PRV_ERROR(no_plugin_arg); + Plugin -> + upgrade(Plugin, State) + end. -spec format_error(any()) -> iolist(). +format_error(no_plugin_arg) -> + io_lib:format("Must give an installed plugin to upgrade as an argument", []); format_error({not_found, Plugin}) -> io_lib:format("Plugin to upgrade not found: ~s", [Plugin]); format_error(Reason) -> @@ -44,25 +50,19 @@ format_error(Reason) -> upgrade(Plugin, State) -> Profiles = rebar_state:current_profiles(State), - Dep = ec_lists:search(fun(Profile) -> - Plugins = rebar_state:get(State, {plugins, Profile}, []), - case find(list_to_atom(Plugin), Plugins) of - false -> - not_found; - P -> - {ok, P} - end - end, Profiles), + case find_plugin(Plugin, Profiles, State) of + not_found -> + Dep = find_plugin(Plugin, [global], State); + Dep -> + Dep + end, case Dep of not_found -> ?PRV_ERROR({not_found, Plugin}); {ok, P, Profile} -> State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR), - {ok, Apps, _State2} = rebar_prv_install_deps:handle_deps(Profile - ,State1 - ,[P] - ,true), + {Apps, _State2} = rebar_prv_install_deps:handle_deps_as_profile(Profile, State1, [P], true), {no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(Apps), ToBuild = rebar_prv_install_deps:cull_compile(Sorted, []), @@ -76,6 +76,17 @@ upgrade(Plugin, State) -> {ok, State} end. +find_plugin(Plugin, Profiles, State) -> + ec_lists:search(fun(Profile) -> + Plugins = rebar_state:get(State, {plugins, Profile}, []), + case find(list_to_atom(Plugin), Plugins) of + false -> + not_found; + P -> + {ok, P} + end + end, Profiles). + find(_Plugin, []) -> false; find(Plugin, [Plugin | _Plugins]) -> diff --git a/src/rebar_prv_release.erl b/src/rebar_prv_release.erl index 982b392..2cf9b23 100644 --- a/src/rebar_prv_release.erl +++ b/src/rebar_prv_release.erl @@ -32,35 +32,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Caller = rebar_state:get(State, caller, api), - Options = rebar_state:command_args(State), - DepsDir = rebar_dir:deps_dir(State), - ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), - LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), - OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), - AllOptions = string:join(["release" | Options], " "), - Cwd = rebar_state:dir(State), - Providers = rebar_state:providers(State), - rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - try - case rebar_state:get(State, relx, []) of - [] -> - relx:main([{lib_dirs, LibDirs} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions); - Config -> - relx:main([{lib_dirs, LibDirs} - ,{config, lists:reverse(Config)} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions) - end, - rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), - {ok, State} - catch - throw:T -> - {error, {rlx_prv_release, T}} - end. + rebar_relx:do(rlx_prv_release, "release", ?PROVIDER, State). -spec format_error(any()) -> iolist(). format_error(Reason) -> diff --git a/src/rebar_prv_relup.erl b/src/rebar_prv_relup.erl new file mode 100644 index 0000000..a4cd8ae --- /dev/null +++ b/src/rebar_prv_relup.erl @@ -0,0 +1,40 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_relup). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, relup). +-define(DEPS, [release]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 relup"}, + {short_desc, "Create relup of releases."}, + {desc, "Create relup of releases."}, + {opts, relx:opt_spec_list()}]), + State1 = rebar_state:add_provider(State, Provider), + {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + rebar_relx:do(rlx_prv_release, "relup", ?PROVIDER, State). + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index 84ad723..3c6369a 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -90,9 +90,10 @@ shell(State) -> %% their application masters never gets the new group leader (held in %% their internal state) maybe_boot_apps(State), - rebar_agent:start_link(State), - %% this call never returns (until user quits shell) - timer:sleep(infinity). + simulate_proc_lib(), + true = register(rebar_agent, self()), + {ok, GenState} = rebar_agent:init(State), + gen_server:enter_loop(rebar_agent, [], GenState, {local, rebar_agent}, hibernate). info() -> "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n". @@ -145,6 +146,11 @@ maybe_boot_apps(State) -> boot_apps(Apps) end. +simulate_proc_lib() -> + FakeParent = spawn_link(fun() -> timer:sleep(infinity) end), + put('$ancestors', [FakeParent]), + 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 diff --git a/src/rebar_prv_tar.erl b/src/rebar_prv_tar.erl index 0c04d72..b3a12c0 100644 --- a/src/rebar_prv_tar.erl +++ b/src/rebar_prv_tar.erl @@ -12,7 +12,7 @@ -include("rebar.hrl"). -define(PROVIDER, tar). --define(DEPS, [compile]). +-define(DEPS, [release]). %% =================================================================== %% Public API @@ -32,30 +32,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - Caller = rebar_state:get(State, caller, api), - Options = rebar_state:command_args(State), - DepsDir = rebar_dir:deps_dir(State), - ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), - LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), - OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), - AllOptions = string:join(["release", "tar" | Options], " "), - Cwd = rebar_state:dir(State), - Providers = rebar_state:providers(State), - rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - case rebar_state:get(State, relx, []) of - [] -> - relx:main([{lib_dirs, LibDirs} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions); - Config -> - relx:main([{lib_dirs, LibDirs} - ,{config, lists:reverse(Config)} - ,{output_dir, OutputDir} - ,{caller, Caller}], AllOptions) - end, - rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), - {ok, State}. + rebar_relx:do(rlx_prv_release, "tar", ?PROVIDER, State). -spec format_error(any()) -> iolist(). format_error(Reason) -> diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index 6838bab..64fe65e 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -43,7 +43,8 @@ do(State) -> Url = rebar_state:get(State, rebar_packages_cdn, "https://s3.amazonaws.com/s3.hex.pm/registry.ets.gz"), {ok, _RequestId} = httpc:request(get, {Url, []}, - [], [{stream, TmpFile}, {sync, true}]), + [], [{stream, TmpFile}, {sync, true}], + rebar), {ok, Data} = file:read_file(TmpFile), Unzipped = zlib:gunzip(Data), ok = file:write_file(HexFile, Unzipped), diff --git a/src/rebar_relx.erl b/src/rebar_relx.erl new file mode 100644 index 0000000..a3adedd --- /dev/null +++ b/src/rebar_relx.erl @@ -0,0 +1,69 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_relx). + +-export([do/4, + format_error/1]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec do(atom(), string(), atom(), rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(Module, Command, Provider, State) -> + Options = rebar_state:command_args(State), + DepsDir = rebar_dir:deps_dir(State), + ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), + LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, + [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), + OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), + AllOptions = string:join([Command | Options], " "), + Cwd = rebar_state:dir(State), + Providers = rebar_state:providers(State), + rebar_hooks:run_all_hooks(Cwd, pre, Provider, Providers, State), + try + case rebar_state:get(State, relx, []) of + [] -> + relx:main([{lib_dirs, LibDirs} + ,{output_dir, OutputDir} + ,{caller, api}], AllOptions); + Config -> + Config1 = update_config(Config), + relx:main([{lib_dirs, LibDirs} + ,{config, Config1} + ,{output_dir, OutputDir} + ,{caller, api}], AllOptions) + end, + rebar_hooks:run_all_hooks(Cwd, post, Provider, Providers, State), + {ok, State} + catch + throw:T -> + {error, {Module, T}} + end. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +%% To handle profiles rebar3 expects the provider to use the first entry +%% in a configuration key-value list as the value of a key if dups exist. +%% This does not work with relx. Some config options must not lose their +%% order (release which has an extends option is one). So here we pull out +%% options that are special so we can reverse the rest so what we expect +%% from a rebar3 profile is what we get on the relx side. +-define(SPECIAL_KEYS, [release, vm_args, sys_config, overlay_vars, lib_dirs]). + +update_config(Config) -> + {Special, Other} = + lists:foldl(fun(Tuple, {SpecialAcc, OtherAcc}) when is_tuple(Tuple) -> + case lists:member(element(1, Tuple), ?SPECIAL_KEYS) of + true -> + {[Tuple | SpecialAcc], OtherAcc}; + false -> + {SpecialAcc, [Tuple | OtherAcc]} + end + end, {[], []}, Config), + lists:reverse(Special) ++ Other. diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 8125026..d0b28de 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -4,6 +4,8 @@ get/2, get/3, set/3, + format_error/1, + has_all_artifacts/1, code_paths/2, code_paths/3, update_code_paths/3, @@ -166,6 +168,9 @@ default(#state_t{default=Opts}) -> default(State, Opts) -> State#state_t{default=Opts}. +format_error({profile_not_list, Profile, Other}) -> + io_lib:format("Profile config must be a list but for profile '~p' config given as:~n~p", [Profile, Other]). + -spec has_all_artifacts(rebar_app_info:t()) -> true | providers:error(). has_all_artifacts(State) -> Artifacts = rebar_state:get(State, artifacts, []), @@ -291,8 +296,13 @@ apply_profiles(State=#state_t{default = Defaults, current_profiles=CurrentProfil lists:foldl(fun(default, OptsAcc) -> OptsAcc; (Profile, OptsAcc) -> - ProfileOpts = dict:from_list(proplists:get_value(Profile, ConfigProfiles, [])), - merge_opts(Profile, ProfileOpts, OptsAcc) + case proplists:get_value(Profile, ConfigProfiles, []) of + OptsList when is_list(OptsList) -> + ProfileOpts = dict:from_list(OptsList), + merge_opts(Profile, ProfileOpts, OptsAcc); + Other -> + throw(?PRV_ERROR({profile_not_list, Profile, Other})) + end end, Defaults, AppliedProfiles), State#state_t{current_profiles = AppliedProfiles, opts=NewOpts}. diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 353fa36..3aa6e90 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -380,4 +380,4 @@ write_file(Output, Data, Force) -> %% Render a binary to a string, using mustache and the specified context %% render(Bin, Context) -> - mustache:render(ec_cnv:to_binary(Bin), Context, [{key_type, atom}]). + bbmustache:render(ec_cnv:to_binary(Bin), Context, [{key_type, atom}]). diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index cc59ed0..ebdf0fe 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -56,7 +56,8 @@ wordsize/0, tup_umerge/2, tup_sort/1, - line_count/1]). + line_count/1, + set_httpc_options/0]). %% for internal use only -export([otp_release/0]). @@ -127,11 +128,13 @@ sh_send(Command0, String, Options0) -> PortSettings = proplists:get_all_values(port_settings, Options) ++ [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], Port = open_port({spawn, Command}, PortSettings), - - %% allow us to send some data to the shell command's STDIN - %% Erlang doesn't let us get any reply after sending an EOF, though... - Port ! {self(), {command, String}}, - port_close(Port). + try + %% allow us to send some data to the shell command's STDIN + %% Erlang doesn't let us get any reply after sending an EOF, though... + Port ! {self(), {command, String}} + after + port_close(Port) + end. %% %% Options = [Option] -- defaults to [use_stdout, abort_on_error] @@ -154,15 +157,19 @@ sh(Command0, Options0) -> Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))), PortSettings = proplists:get_all_values(port_settings, Options) ++ - [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof], ?DEBUG("Port Cmd: ~s\nPort Opts: ~p\n", [Command, PortSettings]), Port = open_port({spawn, Command}, PortSettings), - case sh_loop(Port, OutputHandler, []) of - {ok, _Output} = Ok -> - Ok; - {error, {_Rc, _Output}=Err} -> - ErrorHandler(Command, Err) + try + case sh_loop(Port, OutputHandler, []) of + {ok, _Output} = Ok -> + Ok; + {error, {_Rc, _Output}=Err} -> + ErrorHandler(Command, Err) + end + after + port_close(Port) end. find_files(Dir, Regex) -> @@ -435,10 +442,14 @@ sh_loop(Port, Fun, Acc) -> sh_loop(Port, Fun, Fun(Line ++ "\n", Acc)); {Port, {data, {noeol, Line}}} -> sh_loop(Port, Fun, Fun(Line, Acc)); - {Port, {exit_status, 0}} -> - {ok, lists:flatten(lists:reverse(Acc))}; - {Port, {exit_status, Rc}} -> - {error, {Rc, lists:flatten(lists:reverse(Acc))}} + {Port, eof} -> + Data = lists:flatten(lists:reverse(Acc)), + receive + {Port, {exit_status, 0}} -> + {ok, Data}; + {Port, {exit_status, Rc}} -> + {error, {Rc, Data}} + end end. beam_to_mod(Dir, Filename) -> @@ -657,3 +668,19 @@ maybe_ends_in_comma(H) -> "," ++ Flag -> lists:reverse(Flag); _ -> false end. + +get_http_vars(Scheme) -> + GlobalConfigFile = rebar_dir:global_config(), + Config = rebar_config:consult_file(GlobalConfigFile), + proplists:get_value(Scheme, Config, []). + +set_httpc_options() -> + set_httpc_options(https_proxy, get_http_vars(https_proxy)), + set_httpc_options(proxy, get_http_vars(http_proxy)). + +set_httpc_options(_, []) -> + ok; + +set_httpc_options(Scheme, Proxy) -> + {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy), + httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar). diff --git a/test/rebar_compile_SUITE.erl b/test/rebar_compile_SUITE.erl index 7e72404..3f95e4f 100644 --- a/test/rebar_compile_SUITE.erl +++ b/test/rebar_compile_SUITE.erl @@ -19,7 +19,9 @@ delete_beam_if_source_deleted/1, checkout_priority/1, highest_version_of_pkg_dep/1, - parse_transform_test/1]). + parse_transform_test/1, + erl_first_files_test/1, + mib_test/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -46,7 +48,8 @@ all() -> build_all_srcdirs, recompile_when_hrl_changes, recompile_when_opts_change, dont_recompile_when_opts_dont_change, dont_recompile_yrl_or_xrl, delete_beam_if_source_deleted, - deps_in_path, checkout_priority, highest_version_of_pkg_dep, parse_transform_test]. + deps_in_path, checkout_priority, highest_version_of_pkg_dep, + parse_transform_test, erl_first_files_test, mib_test]. build_basic_app(Config) -> AppDir = ?config(apps, Config), @@ -161,9 +164,10 @@ recompile_when_hrl_changes(Config) -> ModTime = [filelib:last_modified(filename:join([EbinDir, F])) || F <- Files, filename:extension(F) == ".beam"], + timer:sleep(1000), - os:cmd("touch " ++ HeaderFile), + rebar_file_utils:touch(HeaderFile), rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), @@ -272,9 +276,9 @@ delete_beam_if_source_deleted(Config) -> rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]), - SrcDir = filename:join([AppDir, "_build", "default", "lib", Name, "src"]), + _SrcDir = filename:join([AppDir, "_build", "default", "lib", Name, "src"]), ?assert(filelib:is_regular(filename:join(EbinDir, "not_a_real_src_" ++ Name ++ ".beam"))), - file:delete(filename:join(SrcDir, "not_a_real_src_" ++ Name ++ ".erl")), + file:delete(filename:join([AppDir, "src", "not_a_real_src_" ++ Name ++ ".erl"])), rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), @@ -305,6 +309,7 @@ deps_in_path(Config) -> ?assertEqual([], [Path || Path <- code:get_path(), {match, _} <- [re:run(Path, DepName)]]), %% Hope not to find pkg name in there + ?assertEqual([], [Path || Path <- code:get_path(), {match, _} <- [re:run(Path, PkgName)]]), %% Build things @@ -441,3 +446,88 @@ parse_transform_test(Config) -> EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]), true = filelib:is_file(filename:join([EbinDir, "pascal.beam"])). + +erl_first_files_test(Config) -> + AppDir = ?config(apps, Config), + RebarConfig = [{erl_opts, [{parse_transform, mark_time}]}, + {erl_first_files, ["src/mark_time.erl", + "src/b.erl", + "src/d.erl", + "src/a.erl"]}], + + 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]), + rebar_test_utils:write_src_file(AppDir, "a.erl"), + rebar_test_utils:write_src_file(AppDir, "b.erl"), + rebar_test_utils:write_src_file(AppDir, "d.erl"), + rebar_test_utils:write_src_file(AppDir, "e.erl"), + + ExtraSrc = <<"-module(mark_time). " + "-export([parse_transform/2]). " + "parse_transform([Form={attribute,_,module,Mod}|Forms], Options) -> " + " [Form, {attribute,1,number, os:timestamp()} | Forms];" + "parse_transform([Form|Forms], Options) -> " + " [Form | parse_transform(Forms, Options)].">>, + + ok = file:write_file(filename:join([AppDir, "src", "mark_time.erl"]), ExtraSrc), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}), + + EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]), + true = filelib:is_file(filename:join([EbinDir, "mark_time.beam"])), + + code:load_abs(filename:join([EbinDir, "a"])), + code:load_abs(filename:join([EbinDir, "b"])), + code:load_abs(filename:join([EbinDir, "d"])), + code:load_abs(filename:join([EbinDir, "e"])), + A = proplists:get_value(number, a:module_info(attributes)), + B = proplists:get_value(number, b:module_info(attributes)), + D = proplists:get_value(number, d:module_info(attributes)), + E = proplists:get_value(number, e:module_info(attributes)), + ?assertEqual([B,D,A,E], lists:sort([A,B,D,E])). + +mib_test(Config) -> + AppDir = ?config(apps, Config), + + RebarConfig = [], + + 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]), + + MibsSrc = <<"-- SIMPLE-MIB.\n" +"-- This is just a simple MIB used for testing!\n" +"--\n" +"SIMPLE-MIB DEFINITIONS ::= BEGIN\n" +"IMPORTS\n" +" MODULE-IDENTITY, enterprises\n" +" FROM SNMPv2-SMI;\n" +"\n" +"ericsson MODULE-IDENTITY\n" +" LAST-UPDATED\n" +" \"201403060000Z\"\n" +" ORGANIZATION\n" +" \"rebar\"\n" +" CONTACT-INFO\n" +" \"rebar <rebar@example.com>\n" +" or\n" +" whoever is currently responsible for the SIMPLE\n" +" enterprise MIB tree branch (enterprises.999).\"\n" +" DESCRIPTION\n" +" \"This very small module is made available\n" +" for mib-compilation testing.\"\n" +" ::= { enterprises 999 }\n" +"END\n">>, + + ok = filelib:ensure_dir(filename:join([AppDir, "mibs", "dummy"])), + ok = file:write_file(filename:join([AppDir, "mibs", "SIMPLE-MIB.mib"]), MibsSrc), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}), + + %% check a beam corresponding to the src in the extra src_dir exists in ebin + PrivMibsDir = filename:join([AppDir, "_build", "default", "lib", Name, "priv", "mibs"]), + true = filelib:is_file(filename:join([PrivMibsDir, "SIMPLE-MIB.bin"])), + + %% check the extra src_dir was linked into the _build dir + true = filelib:is_dir(filename:join([AppDir, "_build", "default", "lib", Name, "mibs"])). diff --git a/test/rebar_cover_SUITE.erl b/test/rebar_cover_SUITE.erl index 0bead99..1fae92c 100644 --- a/test/rebar_cover_SUITE.erl +++ b/test/rebar_cover_SUITE.erl @@ -8,7 +8,6 @@ flag_coverdata_written/1, config_coverdata_written/1, index_written/1, - config_alt_coverdir/1, flag_verbose/1, config_verbose/1]). @@ -31,7 +30,6 @@ init_per_testcase(_, Config) -> all() -> [flag_coverdata_written, config_coverdata_written, index_written, - config_alt_coverdir, flag_verbose, config_verbose]. flag_coverdata_written(Config) -> @@ -79,23 +77,6 @@ index_written(Config) -> true = filelib:is_file(filename:join([AppDir, "_build", "test", "cover", "index.html"])). -config_alt_coverdir(Config) -> - AppDir = ?config(apps, Config), - - Name = rebar_test_utils:create_random_name("cover_"), - Vsn = rebar_test_utils:create_random_vsn(), - rebar_test_utils:create_eunit_app(AppDir, Name, Vsn, [kernel, stdlib]), - - CoverDir = filename:join(["coverage", "goes", "here"]), - - RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_data_dir, CoverDir}], - rebar_test_utils:run_and_check(Config, - RebarConfig, - ["do", "eunit", "--cover", ",", "cover"], - {ok, [{app, Name}]}), - - true = filelib:is_file(filename:join([CoverDir, "index.html"])). - flag_verbose(Config) -> AppDir = ?config(apps, Config), @@ -118,7 +99,7 @@ config_verbose(Config) -> Vsn = rebar_test_utils:create_random_vsn(), rebar_test_utils:create_eunit_app(AppDir, Name, Vsn, [kernel, stdlib]), - RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_print_enabled, true}], + RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_opts, [verbose]}], rebar_test_utils:run_and_check(Config, RebarConfig, ["do", "eunit", "--cover", ",", "cover"], diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl index afd487e..73c4980 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, {group, git}, {group, pkg}]. +all() -> [sub_app_deps, newly_added_dep, http_proxy_settings, https_proxy_settings, {group, git}, {group, pkg}]. groups() -> [{all, [], [flat, pick_highest_left, pick_highest_right, @@ -33,6 +33,47 @@ init_per_testcase(newly_added_dep, Config) -> rebar_test_utils:init_rebar_state(Config); init_per_testcase(sub_app_deps, Config) -> rebar_test_utils:init_rebar_state(Config); +init_per_testcase(http_proxy_settings, Config) -> + %% Create private rebar.config + Priv = ?config(priv_dir, Config), + GlobalDir = filename:join(Priv, "global"), + GlobalConfigDir = filename:join([GlobalDir, ".config", "rebar3"]), + GlobalConfig = filename:join([GlobalDir, ".config", "rebar3", "rebar.config"]), + + meck:new(rebar_dir, [passthrough]), + meck:expect(rebar_dir, global_config, fun() -> GlobalConfig end), + meck:expect(rebar_dir, global_cache_dir, fun(_) -> GlobalDir end), + + %% 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, + if not SupportsHttpsProxy -> + {skip, https_proxy_unsupported_before_R16}; + SupportsHttpsProxy -> + %% Create private rebar.config + Priv = ?config(priv_dir, Config), + GlobalDir = filename:join(Priv, "global"), + GlobalConfigDir = filename:join([GlobalDir, ".config", "rebar3"]), + GlobalConfig = filename:join([GlobalDir, ".config", "rebar3", "rebar.config"]), + + meck:new(rebar_dir, [passthrough]), + meck:expect(rebar_dir, global_config, fun() -> GlobalConfig end), + meck:expect(rebar_dir, global_cache_dir, fun(_) -> GlobalDir end), + + %% 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 @@ -45,6 +86,12 @@ init_per_testcase(Case, Config) -> {warnings, Warnings} | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))]. +end_per_testcase(https_proxy_settings, Config) -> + meck:unload(rebar_dir), + Config; +end_per_testcase(http_proxy_settings, Config) -> + meck:unload(rebar_dir), + Config; end_per_testcase(_, Config) -> meck:unload(), Config. @@ -223,6 +270,25 @@ newly_added_dep(Config) -> {ok, [{app, Name}, {dep, "a"}, {dep, "b", "1.0.0"}, {dep, "c", "1.0.0"}]}). +http_proxy_settings(_Config) -> + %% Load config + rebar_utils:set_httpc_options(), + rebar3:init_config(), + + %% Assert variable is right + ?assertEqual({ok,{{"localhost", 1234}, []}}, + httpc:get_option(proxy, rebar)). + +https_proxy_settings(_Config) -> + %% Load config + rebar_utils:set_httpc_options(), + rebar3:init_config(), + + %% Assert variable is right + ?assertEqual({ok,{{"localhost", 1234}, []}}, + httpc:get_option(https_proxy, rebar)). + + run(Config) -> {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), rebar_test_utils:run_and_check( diff --git a/test/rebar_hooks_SUITE.erl b/test/rebar_hooks_SUITE.erl index 3908ca1..85ca0e5 100644 --- a/test/rebar_hooks_SUITE.erl +++ b/test/rebar_hooks_SUITE.erl @@ -62,7 +62,7 @@ escriptize_artifacts(Config) -> ]), {ok, RConf} = file:consult(RConfFile), - try rebar_test_utils:run_and_check(Config, RConf, ["compile"], []) + try rebar_test_utils:run_and_check(Config, RConf, ["compile"], return) catch {error, {rebar_prv_compile, @@ -114,7 +114,7 @@ run_hooks_for_plugins(Config) -> rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), PluginName = rebar_test_utils:create_random_name("plugin1_"), - mock_git_resource:mock([{config, [{pre_hooks, [{compile, "touch randomfile"}]}]}]), + mock_git_resource:mock([{config, [{pre_hooks, [{compile, "echo whatsup > randomfile"}]}]}]), RConfFile = rebar_test_utils:create_config(AppDir, [{plugins, [ diff --git a/test/rebar_install_deps_SUITE.erl b/test/rebar_install_deps_SUITE.erl index d1a1118..be42e68 100644 --- a/test/rebar_install_deps_SUITE.erl +++ b/test/rebar_install_deps_SUITE.erl @@ -10,7 +10,8 @@ groups() -> [{all, [], [flat, pick_highest_left, pick_highest_right, pick_smallest1, pick_smallest2, circular1, circular2, circular_skip, - fail_conflict, default_profile, nondefault_profile]}, + fail_conflict, default_profile, nondefault_profile, + nondefault_pick_highest]}, {git, [], [{group, all}]}, {pkg, [], [{group, all}]}]. @@ -48,10 +49,10 @@ end_per_testcase(_, Config) -> Config. format_expected_deps(Deps) -> - [case Dep of - {N,V} -> {dep, N, V}; - N -> {dep, N} - end || Dep <- Deps]. + lists:append([case Dep of + {N,V} -> [{dep, N, V}, {lock, N, V}]; + N -> [{dep, N}, {lock, N}] + end || Dep <- Deps]). %% format: %% {Spec, @@ -125,7 +126,10 @@ deps(nondefault_profile) -> {[{"B", []}, {"C", []}], [], - {ok, ["B", "C"]}}. + {ok, ["B", "C"]}}; +deps(nondefault_pick_highest) -> + %% This is all handled in setup_project + {[],[],{ok,[]}}. setup_project(fail_conflict, Config0, Deps) -> DepsType = ?config(deps_type, Config0), @@ -164,6 +168,34 @@ setup_project(nondefault_profile, Config0, Deps) -> mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}]) end, [{rebarconfig, RebarConf} | Config]; +setup_project(nondefault_pick_highest, Config0, _) -> + DepsType = ?config(deps_type, Config0), + Config = rebar_test_utils:init_rebar_state( + Config0, + "nondefault_pick_highest_"++atom_to_list(DepsType)++"_" + ), + AppDir = ?config(apps, Config), + rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), + DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1", []}]}]), + ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2", []}]), + DefaultTop = rebar_test_utils:top_level_deps(DefaultDeps), + ProfileTop = rebar_test_utils:top_level_deps(ProfileDeps), + RebarConf = rebar_test_utils:create_config( + AppDir, + [{deps, DefaultTop}, + {profiles, [{nondef, [{deps, ProfileTop}]}]}] + ), + case DepsType of + git -> + mock_git_resource:mock( + [{deps, rebar_test_utils:flat_deps(DefaultDeps ++ ProfileDeps)}] + ); + pkg -> + mock_pkg_resource:mock( + [{pkgdeps, rebar_test_utils:flat_pkgdeps(DefaultDeps ++ ProfileDeps)}] + ) + end, + [{rebarconfig, RebarConf} | Config]; setup_project(Case, Config0, Deps) -> DepsType = ?config(deps_type, Config0), Config = rebar_test_utils:init_rebar_state( @@ -200,7 +232,7 @@ circular_skip(Config) -> run(Config). fail_conflict(Config) -> {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), rebar_test_utils:run_and_check( - Config, RebarConfig, ["install_deps"], ?config(expect, Config) + Config, RebarConfig, ["lock"], ?config(expect, Config) ), check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)). @@ -209,7 +241,7 @@ default_profile(Config) -> AppDir = ?config(apps, Config), {ok, Apps} = Expect = ?config(expect, Config), rebar_test_utils:run_and_check( - Config, RebarConfig, ["as", "profile", "install_deps"], Expect + Config, RebarConfig, ["as", "profile", "lock"], Expect ), check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)), BuildDir = filename:join([AppDir, "_build"]), @@ -221,18 +253,30 @@ default_profile(Config) -> || {dep, App} <- Apps], %% A second run to another profile also links default to the right spot rebar_test_utils:run_and_check( - Config, RebarConfig, ["as", "other", "install_deps"], Expect + Config, RebarConfig, ["as", "other", "lock"], Expect ), [?assertMatch({ok, #file_info{type=directory}}, % somehow symlinks return dirs file:read_file_info(filename:join([BuildDir, "other", "lib", App]))) || {dep, App} <- Apps]. nondefault_profile(Config) -> + %% The dependencies here are saved directly to the {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), AppDir = ?config(apps, Config), - {ok, Apps} = Expect = ?config(expect, Config), + {ok, AppLocks} = ?config(expect, Config), + try + rebar_test_utils:run_and_check( + Config, RebarConfig, ["as", "nondef", "lock"], {ok, AppLocks} + ), + error(generated_locks) + catch + error:generated_locks -> error(generated_locks); + _:_ -> ok + end, + Apps = [App || App = {dep, _} <- AppLocks], + Expect = {ok, Apps}, rebar_test_utils:run_and_check( - Config, RebarConfig, ["as", "nondef", "install_deps"], Expect + Config, RebarConfig, ["as", "nondef", "lock"], Expect ), check_warnings(error_calls(), ?config(warnings, Config), ?config(deps_type, Config)), BuildDir = filename:join([AppDir, "_build"]), @@ -244,17 +288,32 @@ nondefault_profile(Config) -> || {dep, App} <- Apps], %% A second run to another profile doesn't link dependencies rebar_test_utils:run_and_check( - Config, RebarConfig, ["as", "other", "install_deps"], Expect + Config, RebarConfig, ["as", "other", "lock"], Expect ), [?assertMatch({error, enoent}, file:read_file_info(filename:join([BuildDir, "default", "lib", App]))) || {dep, App} <- Apps]. +nondefault_pick_highest(Config) -> + {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), + %AppDir = ?config(apps, Config), + rebar_test_utils:run_and_check( + Config, RebarConfig, ["as", "nondef", "lock"], + {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "2"}], "nondef"} + ), + rebar_test_utils:run_and_check( + Config, RebarConfig, ["lock"], + {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1"}, {lock, "C", "1"}], "default"} + ), + rebar_test_utils:run_and_check( + Config, RebarConfig, ["as", "nondef", "lock"], + {ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "2"}], "nondef"} + ). run(Config) -> {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), rebar_test_utils:run_and_check( - Config, RebarConfig, ["install_deps"], ?config(expect, Config) + Config, RebarConfig, ["lock"], ?config(expect, Config) ), check_warnings(warning_calls(), ?config(warnings, Config), ?config(deps_type, Config)). @@ -282,4 +341,3 @@ in_warnings(pkg, Warns, NameRaw, VsnRaw) -> Vsn = iolist_to_binary(VsnRaw), 1 =< length([1 || {_, [AppName, AppVsn]} <- Warns, AppName =:= Name, AppVsn =:= Vsn]). - diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl index 95eb6f6..85bd6f0 100644 --- a/test/rebar_pkg_SUITE.erl +++ b/test/rebar_pkg_SUITE.erl @@ -61,7 +61,7 @@ init_per_testcase(good_disconnect=Name, Config0) -> copy_to_cache(Pkg, Config), meck:unload(httpc), meck:new(httpc, [passthrough, unsticky]), - meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end), + meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end), Config; init_per_testcase(bad_disconnect=Name, Config0) -> Pkg = {<<"goodpkg">>, <<"1.0.0">>}, @@ -71,7 +71,7 @@ init_per_testcase(bad_disconnect=Name, Config0) -> Config = mock_config(Name, Config1), meck:unload(httpc), meck:new(httpc, [passthrough, unsticky]), - meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end), + meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end), Config. end_per_testcase(_, Config) -> @@ -186,9 +186,9 @@ mock_config(Name, Config) -> {ok, PkgContents} = file:read_file(filename:join(?config(data_dir, Config), PkgFile)), meck:new(httpc, [passthrough, unsticky]), meck:expect(httpc, request, - fun(get, {_Url, _Opts}, _, _) when GoodCache -> + fun(get, {_Url, _Opts}, _, _, _) when GoodCache -> {ok, {{Vsn, 304, <<"Not Modified">>}, [{"etag", ?good_etag}], <<>>}}; - (get, {_Url, _Opts}, _, _) -> + (get, {_Url, _Opts}, _, _, _) -> {ok, {{Vsn, 200, <<"OK">>}, [{"etag", ?good_etag}], PkgContents}} end), [{cache_root, CacheRoot}, diff --git a/test/rebar_release_SUITE.erl b/test/rebar_release_SUITE.erl index 3809106..1ef0771 100644 --- a/test/rebar_release_SUITE.erl +++ b/test/rebar_release_SUITE.erl @@ -6,7 +6,8 @@ all() -> [release, dev_mode_release, profile_dev_mode_override_release, - tar]. + tar, + extend_release]. init_per_testcase(Case, Config0) -> Config = rebar_test_utils:init_rebar_state(Config0), @@ -90,3 +91,21 @@ tar(Config) -> ["tar"], {ok, [{release, list_to_atom(Name), Vsn, false}, {tar, Name, Vsn}]} ). + +%% Test that the order of release config args is not lost. If it is extend would fail. +extend_release(Config) -> + AppDir = ?config(apps, Config), + Name = ?config(name, Config), + Vsn = "1.0.0", + {ok, RebarConfig} = + file:consult(rebar_test_utils:create_config(AppDir, + [{relx, [{release, {list_to_atom(Name), Vsn}, + [list_to_atom(Name)]}, + {release, {extended, Vsn, {extend, list_to_atom(Name)}}, + []}, + {lib_dirs, [AppDir]}]}])), + rebar_test_utils:run_and_check( + Config, RebarConfig, + ["release", "-n", "extended"], + {ok, [{release, extended, Vsn, false}]} + ). diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index 9c181d8..4a13e03 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -4,7 +4,7 @@ -export([init_rebar_state/1, init_rebar_state/2, run_and_check/4]). -export([expand_deps/2, flat_deps/1, flat_pkgdeps/1, top_level_deps/1]). -export([create_app/4, create_eunit_app/4, create_empty_app/4, create_config/2]). --export([create_random_name/1, create_random_vsn/0]). +-export([create_random_name/1, create_random_vsn/0, write_src_file/2]). %%%%%%%%%%%%%% %%% Public %%% @@ -56,7 +56,11 @@ run_and_check(Config, RebarConfig, Command, Expect) -> ?assertEqual({error, Reason}, Res); {ok, Expected} -> {ok, _} = Res, - check_results(AppDir, Expected), + check_results(AppDir, Expected, "*"), + Res; + {ok, Expected, ProfileRun} -> + {ok, _} = Res, + check_results(AppDir, Expected, ProfileRun), Res; return -> Res @@ -105,16 +109,22 @@ create_config(AppDir, Contents) -> %% @doc Util to create a random variation of a given name. create_random_name(Name) -> - random:seed(os:timestamp()), + random_seed(), Name ++ erlang:integer_to_list(random:uniform(1000000)). %% @doc Util to create a random variation of a given version. create_random_vsn() -> - random:seed(os:timestamp()), + random_seed(), lists:flatten([erlang:integer_to_list(random:uniform(100)), ".", erlang:integer_to_list(random:uniform(100)), ".", erlang:integer_to_list(random:uniform(100))]). +random_seed() -> + <<A:32, B:32, C:32>> = crypto:rand_bytes(12), + random:seed({A,B,C}). + + + expand_deps(_, []) -> []; expand_deps(git, [{Name, Deps} | Rest]) -> Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}}, @@ -158,9 +168,9 @@ top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) -> %%%%%%%%%%%%%%% %%% Helpers %%% %%%%%%%%%%%%%%% -check_results(AppDir, Expected) -> - BuildDirs = filelib:wildcard(filename:join([AppDir, "_build", "*", "lib"])), - PluginDirs = filelib:wildcard(filename:join([AppDir, "_build", "*", "plugins"])), +check_results(AppDir, Expected, ProfileRun) -> + BuildDirs = filelib:wildcard(filename:join([AppDir, "_build", ProfileRun, "lib"])), + PluginDirs = filelib:wildcard(filename:join([AppDir, "_build", ProfileRun, "plugins"])), GlobalPluginDirs = filelib:wildcard(filename:join([AppDir, "global", "plugins"])), CheckoutsDir = filename:join([AppDir, "_checkouts"]), LockFile = filename:join([AppDir, "rebar.lock"]), @@ -323,12 +333,12 @@ write_app_src_file(Dir, Name, Version, Deps) -> ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)). erl_src_file(Name) -> - io_lib:format("-module(~s).\n" + io_lib:format("-module('~s').\n" "-export([main/0]).\n" "main() -> ok.\n", [filename:basename(Name, ".erl")]). erl_eunitized_src_file(Name) -> - io_lib:format("-module(~s).\n" + io_lib:format("-module('~s').\n" "-export([main/0]).\n" "main() -> ok.\n" "-ifdef(TEST).\n" @@ -338,7 +348,7 @@ erl_eunitized_src_file(Name) -> erl_eunit_suite_file(Name) -> BaseName = filename:basename(Name, ".erl"), - io_lib:format("-module(~s_tests).\n" + io_lib:format("-module('~s_tests').\n" "-compile(export_all).\n" "-ifndef(some_define).\n" "-define(some_define, false).\n" diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl index 79cf29e..f2d4133 100644 --- a/test/rebar_upgrade_SUITE.erl +++ b/test/rebar_upgrade_SUITE.erl @@ -10,7 +10,8 @@ groups() -> pair_a, pair_b, pair_ab, pair_c, pair_all, triplet_a, triplet_b, triplet_c, tree_a, tree_b, tree_c, tree_c2, tree_ac, tree_all, - delete_d, promote, stable_lock, fwd_lock]}, + delete_d, promote, stable_lock, fwd_lock, + compile_upgrade_parity]}, {git, [], [{group, all}]}, {pkg, [], [{group, all}]}]. @@ -404,7 +405,20 @@ upgrades(fwd_lock) -> %% file to include the result post-upgrade, and then %% run a regular lock to see that the lock file is respected %% in deps. - {"any", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}. + {"any", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}; +upgrades(compile_upgrade_parity) -> + {[{"A", "1", [{"D",[{"J",[]}]}, + {"E",[{"I","1",[]}]}]}, + {"B", "1", [{"F",[]}, + {"G",[]}]}, + {"C", "1", [{"H",[]}, + {"I","2",[]}]} + ], + [], + [], + {"", [{"A","1"}, "D", "J", "E", {"I","1"}, + {"B","1"}, "F", "G", + {"C","1"}, "H"]}}. %% TODO: add a test that verifies that unlocking files and then %% running the upgrade code is enough to properly upgrade things. @@ -518,6 +532,22 @@ fwd_lock(Config) -> Config, NewRebarConfig, ["lock", App], Expectation ). +compile_upgrade_parity(Config) -> + AppDir = ?config(apps, Config), + apply(?config(mock, Config), []), + {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), + %% compiling and upgrading should generate the same lockfiles when + %% deps are identical + Lockfile = filename:join([AppDir, "rebar.lock"]), + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, []}), + {ok, CompileLockData1} = file:read_file(Lockfile), + rebar_test_utils:run_and_check(Config, RebarConfig, ["upgrade"], {ok, []}), + {ok, UpgradeLockData} = file:read_file(Lockfile), + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, []}), + {ok, CompileLockData2} = file:read_file(Lockfile), + ?assertEqual(CompileLockData1, CompileLockData2), + ?assertEqual(CompileLockData1, UpgradeLockData). + run(Config) -> apply(?config(mock, Config), []), {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), diff --git a/test/rebar_utils_SUITE.erl b/test/rebar_utils_SUITE.erl index e9b32e2..f04ab63 100644 --- a/test/rebar_utils_SUITE.erl +++ b/test/rebar_utils_SUITE.erl @@ -21,7 +21,8 @@ task_with_flag_with_trailing_comma/1, task_with_flag_with_commas/1, task_with_multiple_flags/1, - special_task_do/1]). + special_task_do/1, + sh_does_not_miss_messages/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -29,7 +30,8 @@ all() -> - [{group, args_to_tasks}]. + [{group, args_to_tasks}, + sh_does_not_miss_messages]. groups() -> [{args_to_tasks, [], [empty_arglist, @@ -118,3 +120,14 @@ special_task_do(_Config) -> "do", "bar,", "baz"]). +sh_does_not_miss_messages(_Config) -> + Source = "~nmain(_) ->~n io:format(\"donotmissme\").~n", + file:write_file("do_not_miss_messages", io_lib:format(Source,[])), + {ok, "donotmissme"} = rebar_utils:sh("escript do_not_miss_messages", []), + AnyMessageRemained = + receive + What -> What + after 100 -> + false + end, + AnyMessageRemained = false. |