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