summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeinz N. Gies <heinz@licenser.net>2016-02-08 11:15:59 -0500
committerHeinz N. Gies <heinz@licenser.net>2016-02-08 11:15:59 -0500
commit2f563041cb248ba0cac27b92da5dcc3e7be27f80 (patch)
treee27ecaf59cfeb0e6406f1f4ce753842ff52855d5
parent91c47db27a3c63fc04940c7c72433062dbadf042 (diff)
parent7fab47dfa05754242790a748bbd303ffe9703e5c (diff)
Merge master
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml4
-rw-r--r--CONTRIBUTING.md371
-rw-r--r--README.md289
-rwxr-xr-xbootstrap46
-rw-r--r--priv/shell-completion/bash/rebar357
-rw-r--r--priv/shell-completion/fish/rebar3.fish22
-rw-r--r--priv/shell-completion/zsh/_rebar324
-rw-r--r--priv/templates/app.erl4
-rw-r--r--priv/templates/escript_mod.erl2
-rw-r--r--priv/templates/escript_rebar.config6
-rw-r--r--priv/templates/gitignore6
-rw-r--r--priv/templates/mod.erl2
-rw-r--r--priv/templates/otp_app.app.src6
-rw-r--r--priv/templates/otp_lib.app.src4
-rw-r--r--priv/templates/plugin.erl4
-rw-r--r--priv/templates/provider.erl4
-rw-r--r--priv/templates/relx_rebar.config4
-rw-r--r--priv/templates/sup.erl2
-rw-r--r--priv/templates/sys.config2
-rw-r--r--rebar.config13
-rw-r--r--rebar.lock10
-rw-r--r--src/rebar.app.src5
-rw-r--r--src/rebar.hrl5
-rw-r--r--src/rebar3.erl28
-rw-r--r--src/rebar_app_info.erl5
-rw-r--r--src/rebar_app_utils.erl42
-rw-r--r--src/rebar_dir.erl31
-rw-r--r--src/rebar_erlc_compiler.erl67
-rw-r--r--src/rebar_file_utils.erl18
-rw-r--r--src/rebar_git_resource.erl4
-rw-r--r--src/rebar_opts.erl6
-rw-r--r--src/rebar_packages.erl67
-rw-r--r--src/rebar_pkg_resource.erl12
-rw-r--r--src/rebar_plugins.erl30
-rw-r--r--src/rebar_prv_app_discovery.erl3
-rw-r--r--src/rebar_prv_clean.erl26
-rw-r--r--src/rebar_prv_common_test.erl847
-rw-r--r--src/rebar_prv_compile.erl23
-rw-r--r--src/rebar_prv_cover.erl50
-rw-r--r--src/rebar_prv_dialyzer.erl44
-rw-r--r--src/rebar_prv_eunit.erl337
-rw-r--r--src/rebar_prv_install_deps.erl5
-rw-r--r--src/rebar_prv_local_install.erl4
-rw-r--r--src/rebar_prv_local_upgrade.erl2
-rw-r--r--src/rebar_prv_new.erl29
-rw-r--r--src/rebar_prv_plugins.erl27
-rw-r--r--src/rebar_prv_shell.erl229
-rw-r--r--src/rebar_prv_update.erl68
-rw-r--r--src/rebar_relx.erl31
-rw-r--r--src/rebar_state.erl19
-rw-r--r--src/rebar_templater.erl77
-rw-r--r--src/rebar_user.erl757
-rw-r--r--src/rebar_utils.erl142
-rw-r--r--test/rebar_as_SUITE.erl22
-rw-r--r--test/rebar_compile_SUITE.erl116
-rw-r--r--test/rebar_cover_SUITE.erl25
-rw-r--r--test/rebar_ct_SUITE.erl860
-rw-r--r--test/rebar_dialyzer_SUITE.erl34
-rw-r--r--test/rebar_dir_SUITE.erl43
-rw-r--r--test/rebar_eunit_SUITE.erl104
-rw-r--r--test/rebar_file_utils_SUITE.erl21
-rw-r--r--test/rebar_new_SUITE.erl68
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/LICENSE.dtl29
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/README.md.dtl9
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/app.erl.dtl27
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/bad_index.template13
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/gitignore.dtl18
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/otp_app.app.src.dtl12
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/rebar.config.dtl2
-rw-r--r--test/rebar_new_SUITE_data/.rebar3/templates/bad_index/sup.erl.dtl35
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/.gitignore19
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/module.erl.dtl2
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/tpl.template7
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/rebar.config2
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.app.src15
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.erl8
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl_prv.erl32
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/rebar.config3
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.app.src15
-rw-r--r--test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.erl13
-rw-r--r--test/rebar_pkg_SUITE.erl45
-rw-r--r--test/rebar_pkg_alias_SUITE.erl4
-rw-r--r--test/rebar_plugins_SUITE.erl77
-rw-r--r--test/rebar_release_SUITE.erl146
-rw-r--r--test/rebar_test_utils.erl15
-rw-r--r--test/rebar_utils_SUITE.erl78
87 files changed, 4487 insertions, 1355 deletions
diff --git a/.gitignore b/.gitignore
index c6be7ef..34b6ef0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+_checkouts
.rebar3
rebar3
_build
diff --git a/.travis.yml b/.travis.yml
index f435a71..630beb2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,15 +13,13 @@ branches:
- master
cache:
directories:
- - $HOME/.cache/rebar3/hex/com/amazonaws/s3/s3.hex.pm/tarballs/packages/
+ - $HOME/.cache/rebar3/hex/default
before_deploy: "rm -rf !(rebar3)"
deploy:
on:
branch: master
condition: $TRAVIS_OTP_RELEASE = R16B03-1
provider: s3
- edge:
- branch: s3-backward-compat
access_key_id: AKIAJAPYAQEFYCYSNL7Q
secret_access_key:
secure: "BUv2KQABv0Q4e8DAVNBRTc/lXHWt27yCN46Fdgo1IrcSSIiP+hq2yXzQcXLbPwkEu6pxUZQtL3mvKbt6l7uw3wFrcRfFAi1PGTITAW8MTmxtwcZIBcHSk3XOzDbkK+fYYcaddszmt7hDzzEFPtmYXiNgnaMIVeynhQLgcCcIRRQ="
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0ce1e93..f175cc2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,129 +1,322 @@
-Contributing to rebar
----------------------
+# Contributing to Rebar3
-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.
+1. [License](#license)
+2. [Submitting a bug](#submitting-a-bug)
+3. [Requesting or implementing a feature](#requesting-or-implementing-a-feature)
+4. [Project Structure](#project-structure)
+5. [Tests](#tests)
+6. [Submitting your changes](#submitting-your-changes)
+ 1. [Code Style](#code-style)
+ 2. [Committing your changes](#committing-your-changes)
+ 3. [Pull Requests and Branching](#pull-requests-and-branching)
+ 4. [Credit](#credit)
-See [Community and Resources](README.md#community-and-resources).
+## License ##
-Code style
-----------
+Rebar3 is licensed under the [Apache License 2.0](LICENSE) for all new code.
+However, since it is built from older code bases, some files still hold other
+free licenses (such as BSD). Where it is the case, the license is added in
+comments.
+
+All files without specific headers can safely be assumed to be under Apache
+2.0.
+
+## Submitting a Bug
+
+Bugs can be submitted to the [Github issue page](https://github.com/rebar/rebar3/issues).
+
+Rebar3 is not perfect software and will be buggy. When submitting a bug, be
+careful to know the following:
+
+- The Erlang version you are running
+- The Rebar3 version you are using
+- The command you were attempting to run
+
+This information can be automatically generated to put into your bug report
+by calling `rebar3 report "my command"`.
+
+You may be asked for further information regarding:
+
+- Your environment, including the Erlang version used to compile rebar3,
+ details about your operating system, where your copy of Erlang was installed
+ from, and so on;
+- Your project, including its structure, and possibly to remove build
+ artifacts to start from a fresh build
+- What it is you are trying to do exactly; we may provide alternative
+ means to do so.
+
+If you can provide an example code base to reproduce the issue on, we will
+generally be able to provide more help, and faster.
+
+All contributors and rebar3 maintainers are generally unpaid developers
+working on the project in their own free time with limited resources. We
+ask for respect and understanding and will try to provide the same back.
+
+## Requesting or implementing a feature
+
+Before requesting or implementing a new feature, please do the following:
+
+- Take a look at our [list of plugins](http://www.rebar3.org/docs/using-available-plugins)
+ to know if the feature isn't already supported by the community.
+- Verify in existing [tickets](https://github.com/rebar/rebar3/issues) whether
+ the feature might already is in the works, has been moved to a plugin, or
+ has already been rejected.
+
+If this is done, open up a ticket. Tell us what is the feature you want,
+why you need it, and why you think it should be in rebar3 itself.
+
+We may discuss details with you regarding the implementation, its inclusion
+within the project or as a plugin. Depending on the feature, we may provide
+full support for it, or ask you to help implement and/or commit to maintaining
+it in the future. We're dedicated to providing a stable build tool, and may
+also ask features to exist as a plugin before being included in core rebar3 --
+the migration path from one to the other is fairly simple and little to no code
+needs rewriting.
+
+## Project Structure
+
+Rebar3 is an escript built around the concept of providers. Providers are the
+modules that do the work to fulfill a user's command. They are documented in
+[the official documentation website](http://www.rebar3.org/docs/plugins#section-provider-interface).
+
+Example provider:
+
+```erlang
+-module(rebar_prv_something).
+
+-behaviour(rebar_provider).
+
+-export([init/1,
+ do/1,
+ format_error/1]).
+
+-define(PROVIDER, something).
+-define(DEPS, []).
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+-spec init(rebar_state:state()) -> {ok, rebar_state:state()}.
+init(State) ->
+ State1 = rebar_state:add_provider(State, rebar_provider:create([
+ {name, ?PROVIDER},
+ {module, ?MODULE},
+ {bare, true},
+ {deps, ?DEPS},
+ {example, "rebar dummy"},
+ {short_desc, "dummy plugin."},
+ {desc, ""},
+ {opts, []}
+ ])),
+ {ok, State1}.
-The following rules apply:
- * Do not introduce trailing whitespace
- * 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.
- Basically, avoid deeply nested functions.
+-spec do(rebar_state:state()) -> {ok, rebar_state:state()}.
+do(State) ->
+ %% Do something
+ {ok, State}.
-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.
+-spec format_error(any()) -> iolist().
+format_error(Reason) ->
+ io_lib:format("~p", [Reason]).
+```
-Where possible, include type specifications for your code so type analysis
-will be as accurate as possible.
+Providers are then listed in `rebar.app.src`, and can be called from
+the command line or as a programmatical API.
-Please add comments around tricky fixes or workarounds so that we can
-easily know why they're there at a glance.
+All commands are therefore implemented in standalone modules. If you call
+`rebar3 <task>`, the module in charge of it is likely located in
+`src/rebar_prv_<task>.erl`.
-Pull requests and branching
----------------------------
+Templates are included in `priv/templates/`
-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.
+The official test suite is Common Test, and tests are located in `test/`.
-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.
+Useful modules include:
+- `rebar_api`, providing an interface for plugins to call into core rebar3
+ functionality
+- `rebar_core`, for initial boot and setup of a project
+- `rebar_config`, handling the configuration of each project.
+- `rebar_app_info`, giving access to the metadata of a specific OTP application
+ in a project.
+- `rebar_base_compiler`, giving a uniform interface to compile `.erl` files.
+- `rebar_dir` for directory handling and management
+- `rebar_file_util` for cross-platform file handling
+- `rebar_state`, the glue holding together a specific build or task run;
+ includes canonical versions of the configuration, profiles, applications,
+ dependencies, and so on.
+- `rebar_utils` for generic tasks and functionality required across
+ multiple providers or modules.
-During the review process, you may be asked to correct or edit a few things
-before a final rebase to merge things.
-
-Please work in feature branches, and do not commit to `master` in your fork.
-
-Provide a clean branch without merge commits.
+## Tests
-Tests
------
+Rebar3 tries to have as many of its features tested as possible. Everything
+that a user can do and should be repeatable in any way should be tested.
-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/`.
+Tests are written using the Common Test framework. Tests for rebar3 can be run
+by calling:
-To run the tests:
-
-```sh
-$ ./bootstrap
+```bash
+$ rebar3 escriptize # or bootstrap
$ ./rebar3 ct
```
-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.
+Most tests are named according to their module name followed by the `_SUITE`
+suffi. Providers are made shorter, such that `rebar_prv_new` is tested in
+`rebar_new_SUITE`.
+
+Most tests in the test suite will rely on calling Rebar3 in its API form,
+then investigating the build output. Because most tests have similar
+requirements, the `test/rebar_test_utils` file contains common code
+to set up test projects, run tasks, and verify artifacts at once.
+
+A basic example can look like:
+
+```erlang
+-module(rebar_some_SUITE).
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+all() -> [checks_success, checks_failure].
+
+init_per_testcase(Case, Config0) ->
+ %% Create a project directory in the test run's priv_dir
+ Config = rebar_test_utils:init_rebar_state(Config0),
+ %% Create toy applications
+ AppDir = ?config(apps, Config),
+ Name = rebar_test_utils:create_random_name("app1_"++atom_to_list(Case)),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+ %% Add the data to the test config
+ [{name, Name} | Config].
+
+end_per_testcase(_, Config) ->
+ Config.
+
+checks_success(Config) ->
+ %% Validates that the application in `name' is successfully compiled
+ Name = ?config(name, Config),
+ rebar_test_utils:run_and_check(Config, [],
+ ["compile"],
+ {ok, [{app, Name}]}).
+
+checks_failure(Config) ->
+ %% Checks that a result fails
+ Command = ["fakecommand", "fake-arg"],
+ rebar_test_utils:run_and_check(
+ Config, [], Command,
+ {error, io_lib:format("Command ~p not found", [fakecommand])}
+ ).
+```
- If such tests prove hard to write, you can ask for help doing that in your
-pull request.
+The general interface to `rebar_test_utils:run_and_check` is
+`run_and_check(CTConfig, RebarConfig, Command, Expect)` where `Expect` can
+be any of:
+
+```erlang
+{ok, OKRes}
+{ok, OKRes, ProfilesUsed}
+{error, Reason}
+
+% where:
+ProfilesUsed :: string() % matching the profiles to validate (defaults to "*")
+OKRes :: {app, Name} % name of an app that is in the build directory
+ | {app, Name, valid} % name of an app that is in the build directory and compiled properly
+ | {app, Name, invalid} % name of an app that didn't compile properly
+ | {dep, Name} % name of a dependency in the build directory
+ | {dep, Name, Vsn} % name of a dependency in the build directory with a specific version
+ | {dep_not_exist, Name} % name of a dependency missing from the build directory
+ | {checkout, Name} % name of an app that is a checkout dependency
+ | {plugin, Name} % name of a plugin in the build directory
+ | {plugin, Name, Vsn} % name of a plugin in the build directory with a specific version
+ | {global_plugin, Name} % name of a global plugin in the build directory
+ | {global_plugin, Name, Vsn} % name of a global plugin in the build directory with a specific version
+ | {lock, Name} % name of a locked dependency
+ | {lock, Name, Vsn} % name of a locked dependency of a specific version
+ | {lock, pkg, Name, Vsn}% name of a locked package of a specific version
+ | {lock, src, Name, Vsn}% name of a locked source dependency of a specific version
+ | {release, Name, Vsn, ExpectedDevMode} % validates a release
+ | {tar, Name, Vsn} % validates a tarball's existence
+ | {file, Filename} % validates the presence of a given file
+ | {dir, Dirname} % validates the presence of a given directory
+Reason :: term() % the exception thrown by rebar3
+```
-For tests having a lot to do with I/O and terminal interaction, consider
-adding them to https://github.com/tsloughter/rebar3_tests
+This generally lets most features be tested fine. Ask for help if you cannot
+figure out how to write tests for your feature or patch.
+## Submitting your changes
-Credit
-------
+While we're not too formal when it comes to pull requests to the project,
+we do appreciate users taking the time to conform to the guidelines that
+follow.
-To give everyone proper credit in addition to the git history, please feel free to append
-your name to `THANKS` in your first contribution.
+We do expect all pull requests submitted to come with [tests](#tests) before
+they are merged. If you cannot figure out how to write your tests properly, ask
+in the pull request for guidance.
+
+### Code Style
-Committing your changes
------------------------
+ * Do not introduce trailing whitespace
+ * Indentation is 4 spaces wide, no tabs.
+ * Try not to introduce lines longer than 80 characters
+ * Write small functions whenever possible, and use descriptive names for
+ functions and variables.
+ * Avoid having too many clauses containing clauses containing clauses.
+ Basically, avoid deeply nested `case ... of` or `try ... catch` expressions.
+ Break them out into functions if possible.
+ * Comment tricky or non-obvious decisions made to explain their rationale.
-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`.
+### Committing your changes
-#### Structuring your commits
+It helps if your commits are structured as follows:
- 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
-the original commit.
+If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup
+commit into the original commit, unless the patch was following a
+maintainer's code review. In such cases, it helps to have separate commits.
-#### Writing Commit Messages
+The reviewer may ask you to later squash the commits together to provide
+a clean commit history before merging in the feature.
-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.
+It's important to write a proper commit title and description. The commit title
+should fir around 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:
+### Pull Requests and Branching
-<pre>
-One line summary (at most 50 characters)
+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.
-Longer description (wrap at 72 characters)
-</pre>
+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.
-##### Commit title/summary
+During the review process, you may be asked to correct or edit a few things
+before a final rebase to merge things. Do send edits as individual commits
+to allow for gradual and partial reviews to be done by reviewers. Once the +1s
+are given, rebasing is appreciated but not mandatory.
-* At most 50 characters
-* What was changed
-* Imperative present tense (Fix, Add, Change)
- * `Fix bug 123`
- * `Add 'foobar' command`
- * `Change default timeout to 123`
-* No period
+Please work in feature branches, and do not commit to `master` in your fork.
+
+Provide a clean branch without merge commits.
-##### Commit description
+If you can, pick a descriptive title for your pull request. When we generate
+changelogs before cutting a release, a script uses the pull request names
+to populate the entries.
-* Wrap at 72 characters
-* Why, explain intention and implementation approach
-* Present tense
+
+### Credit
+
+To give everyone proper credit in addition to the git history, please feel free to append
+your name to `THANKS` in your first contribution.
diff --git a/README.md b/README.md
index 112fa2b..0fa486a 100644
--- a/README.md
+++ b/README.md
@@ -1,171 +1,150 @@
-rebar
-=====
-
-rebar [3.0](#30) is an Erlang build tool that makes it easy to compile and test Erlang
-applications, port drivers and releases.
+# 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 ([hex.pm](http://hex.pm), git, hg, and so on).
-
-3.0 Beta-3
-====
-
-[DOCUMENTATION](http://www.rebar3.org/v3.0/docs)
-
-### Commands
-
-| Command | Description |
-|----------- |------------ |
-| as | Higher-order provider to run multiple tasks in sequence as certain profiles |
-| compile | Build project |
-| 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 |
-| escriptize | Generate escript of project |
-| eunit | Run eunit tests |
-| help | Print help for rebar or task |
-| new | Create new rebar project from templates |
-| path | Print paths to build dirs in current profile |
-| 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 |
-| tree | Print dependency tree |
-| unlock | Unlock dependencies |
-| unstable | Namespace providing commands that are still in flux |
-| 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)
-
-
-### Configuration
-
-Rebar3 uses the following environment variables to adjust behaviour:
-
-* `REBAR_COLOR` - set to `low` or `high` changes the color of the output to be more or less intense
-* `TERM` - used to detect if logs should be plain text or term colored, uses termcap to determin capabilities. If set to `dumb` colors are supressed.
-
-### 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 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
-
-Providers are the modules that do the work to fulfill a user's command.
-
-Example:
-
-```erlang
--module(rebar_prv_something).
-
--behaviour(rebar_provider).
-
--export([init/1,
- do/1,
- format_error/1]).
-
--define(PROVIDER, something).
--define(DEPS, []).
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
--spec init(rebar_state:state()) -> {ok, rebar_state:state()}.
-init(State) ->
- State1 = rebar_state:add_provider(State, rebar_provider:create([{name, ?PROVIDER},
- {module, ?MODULE},
- {bare, false},
- {deps, ?DEPS},
- {example, "rebar dummy"},
- {short_desc, "dummy plugin."},
- {desc, ""},
- {opts, []}])),
- {ok, State1}.
-
--spec do(rebar_state:state()) -> {ok, rebar_state:state()}.
-do(State) ->
- %% Do something
- {ok, State}.
-
--spec format_error(any()) -> iolist().
-format_error(Reason) ->
- io_lib:format("~p", [Reason]).
+1. [What is Rebar3?](#what-is-rebar3)
+2. [Why Rebar3?](#why-rebar3)
+3. [Should I Use Rebar3?](#should-i-use-rebar3)
+4. [Getting Started](#getting-started)
+5. [Documentation](#documentation)
+6. [Features](#features)
+7. [Migrating from rebar2](#migrating-from-rebar2)
+8. [Additional Resources](#additional-resources)
+
+## What is Rebar3
+
+Rebar3 is an Erlang tool that makes it easy to create, develop, and
+release Erlang libraries, applications, and systems in a repeatable manner.
+
+Rebar3 will:
+- respect and enforce standard Erlang/OTP conventions for project
+ structure so they are easily reusable by the community;
+- manage source dependencies and Erlang [packages](http://hex.pm)
+ while ensuring repeatable builds;
+- handle build artifacts, paths, and libraries such that standard
+ development tools can be used without a headache;
+- adapt to projects of all sizes on almost any platform;
+- treat [documentation](http://www.rebar3.org/docs/) as a feature,
+ and errors or lack of documentation as a bug.
+
+Rebar3 is also a self-contained Erlang script. It is easy to distribute or
+embed directly in a project. Tasks or behaviours can be modified or expanded
+with a [plugin system](http://www.rebar3.org/docs/using-available-plugins)
+[flexible enough](http://www.rebar3.org/docs/plugins) that even other languages
+on the Erlang VM will use it as a build tool.
+
+## Why Rebar3
+
+Rebar3 is the spiritual successor to [rebar
+2.x](https://github.com/rebar/rebar), which was the first usable build tool
+for Erlang that ended up seeing widespread community adoption. It however
+had several shortcomings that made it difficult to use with larger projects
+or with teams with users new to Erlang.
+
+Rebar3 was our attempt at improving over the legacy of Rebar 2.x, providing the
+features we felt it was missing, and to provide a better environment in which
+newcomers joining our teams could develop.
+
+## Should I use Rebar3?
+
+If your main language for your system is Erlang, that you value repeatable builds
+and want your various tools to integrate together, we do believe Rebar3 is the
+best experience you can get.
+
+## Getting Started
+
+A [getting started guide is maintained on the offcial documentation website](http://www.rebar3.org/docs/getting-started),
+but installing rebar3 can be done by any of the ways described below
+
+Nightly compiled version:
+```bash
+$ wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3
```
+From Source (assuming you have a full Erlang install):
-Building
---------
-
-Recommended installation of [Erlang/OTP](http://www.erlang.org) is source built using [erln8](http://erln8.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
-
-To build rebar you will need a working installation of Erlang R15 (or later).
-
-Should you want to clone the rebar repository, you will also require git.
-
-#### Downloading
-
-You can download a pre-built binary version of rebar3 based on the last commit from:
-
-https://s3.amazonaws.com/rebar3/rebar3
-
-#### Bootstrapping rebar3
-
-```sh
-$ git clone https://github.com/rebar/rebar3
+```bash
+$ git clone https://github.com/rebar/rebar3.git
$ cd rebar3
$ ./bootstrap
```
-### Developing on rebar3
+Stable versions can be obtained from the [releases page](https://github.com/rebar/rebar3/releases).
-When developing you can simply run `escriptize` to build your changes but the new escript is under `_build/default/bin/rebar3`
+The rebar3 escript can also extract itself with a run script under the user's home directory:
-```sh
-$ ./rebar3 escriptize
-$ _build/default/bin/rebar3
+```bash
+$ ./rebar3 local install
+===> Extracting rebar3 libs to ~/.cache/rebar3/lib...
+===> Writing rebar3 run script ~/.cache/rebar3/bin/rebar3...
+===> Add to $PATH for use: export PATH=$PATH:~/.cache/rebar3/bin
```
-Contributing to rebar
-=====================
-
-Please refer to [CONTRIBUTING](CONTRIBUTING.md).
-
-Community and Resources
------------------------
+To keep it up to date after you've installed rebar3 this way you can use `rebar3 local upgrade` which
+fetches the latest nightly and extracts to the same place as above.
+
+Rebar3 may also be available on various OS-specific package managers such as
+FreeBSD Ports. Those are maintained by the community and Rebar3 maintainers
+themselves are generally not involved in that process.
+
+If you do not have a full Erlang install, we using [erln8](http://erln8.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.
+
+## Documentation
+
+Rebar3 documentation is maintained on [http://www.rebar3.org/docs](http://www.rebar3.org/docs)
+
+## Features
+
+Rebar3 supports the following features or tools by default, and may provide many
+others via the plugin ecosystem:
+
+| features | Description |
+|--------------------- |------------ |
+| Command composition | Rebar3 allows multiple commands to be run in sequence by calling `rebar3 do <task1>,<task2>,...,<taskN>`. |
+| Command dependencies | Rebar3 commands know their own dependencies. If a test run needs to fetch dependencies and build them, it will do so. |
+| Command namespaces | Allows multiple tools or commands to share the same name. |
+| Compiling | Build the project, including fetching all of its dependencies by calling `rebar3 compile` |
+| Clean up artifacts | Remove the compiled beam files from a project with `rebar3 clean` or just remove the `_build` directory to remove *all* compilation artifacts |
+| Code Coverage | Various commands can be instrumented to accumulate code coverage data (such as `eunit` or `ct`). Reports can be generated with `rebar3 cover` |
+| Common Test | The test framework can be run by calling `rebar3 ct` |
+| Dependencies | Rebar3 maintains local copies of dependencies on a per-project basis. They are fetched deterministically, can be locked, upgraded, fetched from source, packages, or from local directories. See [Dependencies on the documentation website](http://www.rebar3.org/docs/dependencies). Call `rebar3 tree` to show the whole dependency tree. |
+| Documentation | Print help for rebar3 itself (`rebar3 help`) or for a specific task (`rebar3 help <task>`). Full reference at [www.rebar3.org](http://www.rebar3.org/docs). |
+| Dialyzer | Run the Dialyzer analyzer on the project with `rebar3 dialyzer`. Base PLTs for each version of the language will be cached and reused for faster analysis |
+| Edoc | Generate documentation using edoc with `rebar3 edoc` |
+| Escript generation | Rebar3 can be used to generate [escripts](http://www.erlang.org/doc/man/escript.html) providing an easy way to run all your applications on a system where Erlang is installed |
+| Eunit | The test framework can be run by calling `rebar3 eunit` |
+| Locked dependencies | Dependencies are going to be automatically locked to ensure repeatable builds. Versions can be changed with `rebar3 upgrade` or `rebar3 upgrade <app>`, or locks can be released altogether with `rebar3 unlock`. |
+| Packages | [Hex packages](http://hex.pm) can be listed with `rebar3 pkgs`. They can be used as dependencies, will be cached locally for faster usage, and a local index will be used and updated with `rebar3 update`. |
+| Path | While paths are managed automatically, you can print paths to the current build directories with `rebar3 path`. |
+| Plugins | Rebar3 can be fully extended with [plugins](#http://www.rebar3.org/docs/using-available-plugins). List or upgrade plugins by using the plugin namespace (`rebar3 plugins`). |
+| Profiles | Rebar3 can have subconfiguration options for different profiles, such as `test` or `prod`. These allow specific dependencies or compile options to be used in specific contexts. See [Profiles](http://www.rebar3.org/docs/profiles) in the docs. |
+| Releases | Rebar3 supports [building releases](http://www.rebar3.org/docs/releases) with the `relx` tool, providing a way to ship fully self-contained Erlang systems. Release update scripts for live code updates can also be generated. |
+| Shell | A full shell with your applications available can be started with `rebar3 shell`. From there, call tasks as `r3:do(compile)` to automatically recompile and reload the code without interruption |
+| Tarballs | Releases can be packaged into tarballs ready to be deployed. |
+| Templates | Configurable templates ship out of the box (try `rebar3 new` for a list or `rebar3 new help <template>` for a specific one). [Custom templates](http://www.rebar3.org/docs/using-templates) are also supported, and plugins can also add their own. |
+| Unstable namespace | We use a namespace to provide commands that are still in flux, allowing to test more experimental features we are working on. See `rebar3 unstable`. |
+| Xref | Run cross reference analysis on the project with [xref](http://www.erlang.org/doc/apps/tools/xref_chapter.html) by calling `rebar3 xref`. |
+
+## Migrating From rebar2
+
+The grievances we had with Rebar 2.x were not fixable without breaking
+compatibility in some very important ways.
+
+A full guide titled [From Rebar 2.x to Rebar3](http://www.rebar3.org/docs/from-rebar-2x-to-rebar3)
+is provided on the documentation website.
+
+Notable modifications include mandating a more standard set of directory
+structures, changing the handling of dependencies, moving some compilers (such
+as C, Diameter, ErlyDTL, or ProtoBuffs) to
+[plugins](http://www.rebar3.org/docs/using-available-plugins) rather than
+maintaining them in core rebar, and moving release builds from reltool to
+relx.
+
+## Additional Resources
In case of problems that cannot be solved through documentation or examples, you
may want to try to contact members of the community for help. The community is
@@ -188,3 +167,5 @@ General rebar community resources and links:
- #rebar on [irc.freenode.net](http://freenode.net/)
- [issues](https://github.com/rebar/rebar3/issues)
- [Documentation](http://www.rebar3.org/v3.0/docs)
+
+To contribute to rebar3, please refer to [CONTRIBUTING](CONTRIBUTING.md).
diff --git a/bootstrap b/bootstrap
index ff4e62a..b20d0d3 100755
--- a/bootstrap
+++ b/bootstrap
@@ -2,8 +2,7 @@
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ft=erlang ts=4 sw=4 et
-
-main(_Args) ->
+main(_) ->
application:start(crypto),
application:start(asn1),
application:start(public_key),
@@ -34,7 +33,14 @@ main(_Args) ->
setup_env(),
os:putenv("REBAR_PROFILE", "bootstrap"),
- rebar3:run(["update"]),
+ RegistryFile = default_registry_file(),
+ case filelib:is_file(RegistryFile) of
+ true ->
+ ok;
+ false ->
+ rebar3:run(["update"])
+ end,
+
{ok, State} = rebar3:run(["compile"]),
reset_env(),
os:putenv("REBAR_PROFILE", ""),
@@ -62,6 +68,11 @@ main(_Args) ->
ok
end.
+default_registry_file() ->
+ {ok, [[Home]]} = init:get_argument(home),
+ CacheDir = filename:join([Home, ".cache", "rebar3"]),
+ filename:join([CacheDir, "hex", "default", "registry"]).
+
fetch_and_compile({Name, ErlFirstFiles}, Deps) ->
case lists:keyfind(Name, 1, Deps) of
{Name, Vsn} ->
@@ -69,19 +80,30 @@ fetch_and_compile({Name, ErlFirstFiles}, Deps) ->
{Name, _, Source} ->
ok = fetch(Source, Name)
end,
+
+ %% Hack: erlware_commons depends on a .script file to check if it is being built with
+ %% rebar2 or rebar3. But since rebar3 isn't built yet it can't get the vsn with get_key.
+ %% So we simply make sure that file is deleted before compiling
+ file:delete("_build/default/lib/erlware_commons/rebar.config.script"),
+
compile(Name, ErlFirstFiles).
fetch({pkg, Name, Vsn}, App) ->
Dir = filename:join([filename:absname("_build/default/lib/"), App]),
- 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])
+ case filelib:is_dir(Dir) of
+ false ->
+ 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;
+ true ->
+ io:format("Dependency ~s already exists~n", [Name])
end.
extract(Binary) ->
diff --git a/priv/shell-completion/bash/rebar3 b/priv/shell-completion/bash/rebar3
index 87ee9eb..3cd3cd7 100644
--- a/priv/shell-completion/bash/rebar3
+++ b/priv/shell-completion/bash/rebar3
@@ -10,7 +10,8 @@ _rebar3()
if [[ ${prev} == rebar3 ]] ; then
sopts="-h -v"
lopts="--help --version"
- cmdsnvars="as \
+ cmdsnvars=" \
+ as \
clean \
compile \
cover \
@@ -37,7 +38,8 @@ _rebar3()
update \
upgrade \
version \
- xref"
+ xref \
+ "
elif [[ ${prev} == as ]] ; then
:
elif [[ ${prev} == clean ]] ; then
@@ -50,15 +52,12 @@ _rebar3()
lopts="--reset --verbose"
elif [[ ${prev} == ct ]] ; then
sopts="-c -v"
- lopts="--dir \
+ lopts=" \
+ --dir \
--suite \
--group \
--case \
- --spec \
- --join_specs \
- --label \
--config \
- --userconfig \
--allow_user_terms \
--logdir \
--logopts \
@@ -66,21 +65,21 @@ _rebar3()
--silent_connections \
--stylesheet \
--cover \
- --cover_spec \
- --cover_stop \
- --event_handler \
- --include \
- --abort_if_missing_suites \
- --multiply_timetraps \
- --scale_timetraps \
- --create_priv_dir \
--repeat \
--duration \
--until \
--force_stop \
--basic_html \
- --ct_hooks \
- --verbose"
+ --stylesheet \
+ --decrypt_key \
+ --decrypt_file \
+ --abort_if_missing_suites \
+ --multiply_timetraps \
+ --scale_timetraps \
+ --create_priv_dir \
+ --verbose \
+ --auto_compile \
+ "
elif [[ ${prev} == deps ]] ; then
:
elif [[ ${prev} == dialyzer ]] ; then
@@ -102,7 +101,8 @@ _rebar3()
lopts="--force"
elif [[ ${prev} == path ]] ; then
sopts="-s"
- lopts="--app \
+ lopts=" \
+ --app \
--base \
--bin \
--ebin \
@@ -110,14 +110,16 @@ _rebar3()
--priv \
--separator \
--src \
- --rel"
+ --rel \
+ "
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 \
+ lopts=" \
+ --relname \
--relvsn \
--goal \
--upfrom \
@@ -136,10 +138,12 @@ _rebar3()
--sys_config \
--system_libs \
--version \
- --root"
+ --root \
+ "
elif [[ ${prev} == relup ]] ; then
sopts="-n -v -g -u -o -h -l -p -V -d -i -a -c -r"
- lopts="--relname \
+ lopts=" \
+ --relname \
--relvsn \
--goal \
--upfrom \
@@ -158,14 +162,16 @@ _rebar3()
--sys_config \
--system_libs \
--version \
- --root"
+ --root \
+ "
elif [[ ${prev} == report ]] ; then
:
elif [[ ${prev} == shell ]] ; then
:
elif [[ ${prev} == tar ]] ; then
sopts="-n -v -g -u -o -h -l -p -V -d -i -a -c -r"
- lopts="--relname \
+ lopts=" \
+ --relname \
--relvsn \
--goal \
--upfrom \
@@ -184,7 +190,8 @@ _rebar3()
--sys_config \
--system_libs \
--version \
- --root"
+ --root \
+ "
elif [[ ${prev} == tree ]] ; then
sopts="-v"
lopts="--verbose"
diff --git a/priv/shell-completion/fish/rebar3.fish b/priv/shell-completion/fish/rebar3.fish
index 0d5d302..7b63e20 100644
--- a/priv/shell-completion/fish/rebar3.fish
+++ b/priv/shell-completion/fish/rebar3.fish
@@ -90,10 +90,28 @@ complete -f -c 'rebar3' -n '__fish_rebar3_needs_command' -a ct -d "Run Common Te
complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l dir -d "Compile and run all test suites in the specified directories."
complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l suites -d "Compile and run all test suites specified. Must be specified by full path, either absolute or relative to the current directory."
complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l group -d "Test groups to run."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l label -d "Test label."
complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l config -d "Config files to use when running tests."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l allow_user_terms -d "Allow user defined terms in config files."
complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l logdir -d "The directory in which test logs will be written. Default: _build/test/logs"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -s v -l verbose -d "Enable verbose output. Default: false"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -s c -l cover -d "Generate cover data"
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l logopts -d "Options for common test logging."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l verbosity -d "Verbosity."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -s c -l cover -d "Generate cover data."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l include -d "Include folders."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l repeat -d "How often to repeat tests."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l duration -d "Max runtime (format: HHMMSS)."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l until -d "Run until (format: HHMMSS)."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l force_stop -d "Force stop on test timeout."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l basic_html -d "Show basic HTML."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l stylesheet -d "CSS stylesheet to apply to html output."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l decrypt_key -d "Path to key for decrypting config."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l decrypt_file -d "Path to file containing key for decrypting config."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l abort_if_missing_suites -d "Abort if suites are missing."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l multiply_timetraps -d "Multiply timetraps."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l scale_timetraps -d "Scale timetraps."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l create_priv_dir -d "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -s v -l verbose -d "Enable verbose output. Default: false."
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command ct' -l auto_compile -d "Let common test compile test suites instead of rebar3."
complete -f -c 'rebar3' -n '__fish_rebar3_needs_command' -a deps -d "List dependencies"
diff --git a/priv/shell-completion/zsh/_rebar3 b/priv/shell-completion/zsh/_rebar3
index d4e1c35..f0fb351 100644
--- a/priv/shell-completion/zsh/_rebar3
+++ b/priv/shell-completion/zsh/_rebar3
@@ -43,33 +43,27 @@ _rebar3 () {
'(--suite)--suite[List of test suites to run]:suites' \
'(--group)--group[List of test groups to run]:groups' \
'(--case)--case[List of test cases to run]:cases' \
- '(--spec)--spec[List of test specs to run]:specs' \
- '(--join_specs)--join_specs' \
'(--label)--label[Test label]:label' \
'(--config)--config[List of config files]:config files:_files' \
- '(--userconfig)--userconfig' \
'(--allow_user_terms)--allow_user_terms' \
'(--logdir)--logdir[Log folder]:log folder:_files -/' \
'(--logopts)--logopts' \
'(--verbosity)--verbosity[Verbosity]:verbosity' \
- '(--silent_connections)--silent_connections' \
- '(--stylesheet)--stylesheet[Stylesheet to use for test results]:stylesheet:_files' \
'(-c --cover)'{-c,--cover}'[Generate cover data]' \
- '(--cover_spec)--cover_spec[Cover file to use]:cover file:_files' \
- '(--cover_stop)--cover_stop' \
- '(--event_handler)--event_handler[Event handlers to attach to the runner]:event handlers' \
- '(--include)--include[Include folder]:include directory:_files -/' \
- '(--abort_if_missing_suites)--abort_if_missing_suites[Abort if suites are missing]:abort missing suites:(true false)' \
- '(--multiply_timetraps)--multiply_timetraps' \
- '(--scale_timetraps)--scale_timetraps' \
- '(--create_priv_dir)--create_priv_dir' \
'(--repeat)--repeat[How often to repeat tests]:repeat test count' \
'(--duration)--duration[Max runtime (format: HHMMSS)]:max run time' \
'(--until)--until[Run until (format: HHMMSS)]:run until time' \
- '(--force_stop)--force_stop[Force stop after time]' \
+ '(--force_stop)--force_stop[Force stop on test timeout]:skip_rest' \
'(--basic_html)--basic_html[Show basic HTML]' \
- '(--ct_hooks)--ct_hooks:ct hooks' \
+ '(--stylesheet)--stylesheet[Stylesheet to use for test results]:stylesheet:_files' \
+ '(--decrypt_key)--decrypt_key[Path to key for decrypting config]:decrypt key:_files' \
+ '(--decrypt_file)--decrypt_file[Path to file containing key for decrypting config]:decrypt file:_files' \
+ '(--abort_if_missing_suites)--abort_if_missing_suites[Abort if suites are missing]:abort missing suites:(true false)' \
+ '(--multiply_timetraps)--multiply_timetraps' \
+ '(--scale_timetraps)--scale_timetraps' \
+ '(--create_priv_dir)--create_priv_dir' \
'(-v --verbose)'{-v,--verbose}'[Print coverage analysis]' \
+ '(--auto_compile)--auto_compile' \
&& ret=0
;;
(deps)
diff --git a/priv/templates/app.erl b/priv/templates/app.erl
index 2e7b909..83eb9a3 100644
--- a/priv/templates/app.erl
+++ b/priv/templates/app.erl
@@ -3,7 +3,7 @@
%% @end
%%%-------------------------------------------------------------------
--module('{{name}}_app').
+-module({{name}}_app).
-behaviour(application).
@@ -16,7 +16,7 @@
%%====================================================================
start(_StartType, _StartArgs) ->
- '{{name}}_sup':start_link().
+ {{name}}_sup:start_link().
%%--------------------------------------------------------------------
stop(_State) ->
diff --git a/priv/templates/escript_mod.erl b/priv/templates/escript_mod.erl
index dd2feb8..f8a779b 100644
--- a/priv/templates/escript_mod.erl
+++ b/priv/templates/escript_mod.erl
@@ -1,4 +1,4 @@
--module('{{name}}').
+-module({{name}}).
%% API exports
-export([main/1]).
diff --git a/priv/templates/escript_rebar.config b/priv/templates/escript_rebar.config
index c0a3301..196f835 100644
--- a/priv/templates/escript_rebar.config
+++ b/priv/templates/escript_rebar.config
@@ -2,9 +2,9 @@
{deps, []}.
{escript_incl_apps,
- ['{{name}}']}.
-{escript_top_level_app, '{{name}}'}.
-{escript_name, '{{name}}'}.
+ [{{name}}]}.
+{escript_top_level_app, {{name}}}.
+{escript_name, {{name}}}.
{escript_emu_args, "%%! +sbtu +A0\n"}.
%% Profiles
diff --git a/priv/templates/gitignore b/priv/templates/gitignore
index a939dce..ced0c5e 100644
--- a/priv/templates/gitignore
+++ b/priv/templates/gitignore
@@ -11,9 +11,5 @@ ebin
log
erl_crash.dump
.rebar
-_rel
-_deps
-_plugins
-_tdeps
logs
-_build \ No newline at end of file
+_build
diff --git a/priv/templates/mod.erl b/priv/templates/mod.erl
index 208307e..2f5e09e 100644
--- a/priv/templates/mod.erl
+++ b/priv/templates/mod.erl
@@ -1,4 +1,4 @@
--module('{{name}}').
+-module({{name}}).
%% API exports
-export([]).
diff --git a/priv/templates/otp_app.app.src b/priv/templates/otp_app.app.src
index 09e4a48..c18f82c 100644
--- a/priv/templates/otp_app.app.src
+++ b/priv/templates/otp_app.app.src
@@ -1,8 +1,8 @@
-{application, '{{name}}',
+{application, {{name}},
[{description, "{{desc}}"},
{vsn, "0.1.0"},
{registered, []},
- {mod, {'{{name}}_app', []}},
+ {mod, { {{name}}_app, []}},
{applications,
[kernel,
stdlib
@@ -10,7 +10,7 @@
{env,[]},
{modules, []},
- {contributors, []},
+ {maintainers, []},
{licenses, []},
{links, []}
]}.
diff --git a/priv/templates/otp_lib.app.src b/priv/templates/otp_lib.app.src
index f07293e..5b98a51 100644
--- a/priv/templates/otp_lib.app.src
+++ b/priv/templates/otp_lib.app.src
@@ -1,4 +1,4 @@
-{application, '{{name}}',
+{application, {{name}},
[{description, "{{desc}}"},
{vsn, "0.1.0"},
{registered, []},
@@ -9,7 +9,7 @@
{env,[]},
{modules, []},
- {contributors, []},
+ {maintainers, []},
{licenses, []},
{links, []}
]}.
diff --git a/priv/templates/plugin.erl b/priv/templates/plugin.erl
index c6e5e40..218960d 100644
--- a/priv/templates/plugin.erl
+++ b/priv/templates/plugin.erl
@@ -1,8 +1,8 @@
--module('{{name}}').
+-module({{name}}).
-export([init/1]).
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
- {ok, State1} = '{{name}}_prv':init(State),
+ {ok, State1} = {{name}}_prv:init(State),
{ok, State1}.
diff --git a/priv/templates/provider.erl b/priv/templates/provider.erl
index 669df83..7639d97 100644
--- a/priv/templates/provider.erl
+++ b/priv/templates/provider.erl
@@ -1,8 +1,8 @@
--module('{{name}}_prv').
+-module({{name}}_prv).
-export([init/1, do/1, format_error/1]).
--define(PROVIDER, '{{name}}').
+-define(PROVIDER, {{name}}).
-define(DEPS, [app_discovery]).
%% ===================================================================
diff --git a/priv/templates/relx_rebar.config b/priv/templates/relx_rebar.config
index da32819..81b7dbe 100644
--- a/priv/templates/relx_rebar.config
+++ b/priv/templates/relx_rebar.config
@@ -1,8 +1,8 @@
{erl_opts, [debug_info]}.
{deps, []}.
-{relx, [{release, {'{{name}}', "0.1.0"},
- ['{{name}}',
+{relx, [{release, { {{name}}, "0.1.0" },
+ [{{name}},
sasl]},
{sys_config, "./config/sys.config"},
diff --git a/priv/templates/sup.erl b/priv/templates/sup.erl
index cd9192a..a2e7209 100644
--- a/priv/templates/sup.erl
+++ b/priv/templates/sup.erl
@@ -3,7 +3,7 @@
%% @end
%%%-------------------------------------------------------------------
--module('{{name}}_sup').
+-module({{name}}_sup).
-behaviour(supervisor).
diff --git a/priv/templates/sys.config b/priv/templates/sys.config
index 7fd6bcb..d892fd6 100644
--- a/priv/templates/sys.config
+++ b/priv/templates/sys.config
@@ -1,3 +1,3 @@
[
- {'{{name}}', []}
+ { {{name}}, []}
].
diff --git a/rebar.config b/rebar.config
index 44fe066..52c2f80 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,14 +1,16 @@
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 ft=erlang et
-{deps, [{erlware_commons, "0.17.0"},
+{deps, [{erlware_commons, "0.18.0"},
{ssl_verify_hostname, "1.0.5"},
- {certifi, "0.1.1"},
- {providers, "1.5.0"},
+ {certifi, "0.3.0"},
+ {providers, "1.6.0"},
{getopt, "0.8.2"},
{bbmustache, "1.0.4"},
- {relx, "3.7.1"},
- {cf, "0.2.1"}]}.
+ {relx, "3.15.0"},
+ {cf, "0.2.1"},
+ {cth_readable, "1.2.0"},
+ {eunit_formatters, "0.3.1"}]}.
{escript_name, rebar3}.
{escript_emu_args, "%%! +sbtu +A0\n"}.
@@ -18,7 +20,6 @@
{"rebar/priv/templates/*", "_build/default/lib/"}]}.
{erl_opts, [{platform_define, "^[0-9]+", namespaced_types},
- {platform_define, "^R1[4|5]", no_list_dir_all},
no_debug_info,
warnings_as_errors]}.
diff --git a/rebar.lock b/rebar.lock
index 7f98aa6..fb4788b 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,8 +1,10 @@
[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0},
- {<<"certifi">>,{pkg,<<"certifi">>,<<"0.1.1">>},0},
+ {<<"certifi">>,{pkg,<<"certifi">>,<<"0.3.0">>},0},
{<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0},
- {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.17.0">>},0},
+ {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.0">>},0},
+ {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.18.0">>},0},
+ {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0},
- {<<"providers">>,{pkg,<<"providers">>,<<"1.5.0">>},0},
- {<<"relx">>,{pkg,<<"relx">>,<<"3.7.1">>},0},
+ {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0},
+ {<<"relx">>,{pkg,<<"relx">>,<<"3.15.0">>},0},
{<<"ssl_verify_hostname">>,{pkg,<<"ssl_verify_hostname">>,<<"1.0.5">>},0}].
diff --git a/src/rebar.app.src b/src/rebar.app.src
index 5ab3ddd..58fee02 100644
--- a/src/rebar.app.src
+++ b/src/rebar.app.src
@@ -25,8 +25,11 @@
bbmustache,
ssl_verify_hostname,
certifi,
+ cth_readable,
relx,
- inets]},
+ cf,
+ inets,
+ eunit_formatters]},
{env, [
%% Default log level
{log_level, warn},
diff --git a/src/rebar.hrl b/src/rebar.hrl
index 8ad0faa..f4e7f5e 100644
--- a/src/rebar.hrl
+++ b/src/rebar.hrl
@@ -22,8 +22,9 @@
-define(DEFAULT_TEST_DEPS_DIR, "test/lib").
-define(DEFAULT_RELEASE_DIR, "rel").
-define(DEFAULT_CONFIG_FILE, "rebar.config").
--define(DEFAULT_CDN, "https://s3.amazonaws.com/s3.hex.pm/tarballs").
--define(DEFAULT_HEX_REGISTRY, "https://s3.amazonaws.com/s3.hex.pm/registry.ets.gz").
+-define(DEFAULT_CDN, "https://s3.amazonaws.com/s3.hex.pm/").
+-define(REMOTE_PACKAGE_DIR, "tarballs").
+-define(REMOTE_REGISTRY_FILE, "registry.ets.gz").
-define(LOCK_FILE, "rebar.lock").
-define(PACKAGE_INDEX_VERSION, 3).
diff --git a/src/rebar3.erl b/src/rebar3.erl
index 2b73844..879378e 100644
--- a/src/rebar3.erl
+++ b/src/rebar3.erl
@@ -105,25 +105,35 @@ run_aux(State, RawArgs) ->
rebar_state:apply_profiles(State, [list_to_atom(Profile)])
end,
+ rebar_utils:check_min_otp_version(rebar_state:get(State1, minimum_otp_vsn, undefined)),
+ rebar_utils:check_blacklisted_otp_versions(rebar_state:get(State1, blacklisted_otp_vsns, undefined)),
+
+ State2 = case os:getenv("HEX_CDN") of
+ false ->
+ State1;
+ CDN ->
+ rebar_state:set(State1, rebar_packages_cdn, CDN)
+ end,
+
%% bootstrap test profile
- State2 = rebar_state:add_to_profile(State1, test, test_state(State1)),
+ State3 = rebar_state:add_to_profile(State2, test, test_state(State1)),
%% 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)),
+ State4 = rebar_state:set(State3, base_dir,
+ filename:join(filename:absname(rebar_state:dir(State3)), BaseDir)),
{ok, Providers} = application:get_env(rebar, providers),
%% Providers can modify profiles stored in opts, so set default after initializing providers
- State4 = rebar_state:create_logic_providers(Providers, State3),
- State5 = rebar_plugins:project_apps_install(State4),
- State6 = rebar_state:default(State5, rebar_state:opts(State5)),
+ State5 = rebar_state:create_logic_providers(Providers, State4),
+ State6 = rebar_plugins:top_level_install(State5),
+ State7 = rebar_state:default(State6, rebar_state:opts(State6)),
{Task, Args} = parse_args(RawArgs),
- State7 = rebar_state:code_paths(State6, default, code:get_path()),
+ State8 = rebar_state:code_paths(State7, default, code:get_path()),
- rebar_core:init_command(rebar_state:command_args(State7, Args), Task).
+ rebar_core:init_command(rebar_state:command_args(State8, Args), Task).
init_config() ->
%% Initialize logging system
@@ -339,4 +349,4 @@ safe_define_test_macro(Opts) ->
test_defined([{d, 'TEST'}|_]) -> true;
test_defined([{d, 'TEST', true}|_]) -> true;
test_defined([_|Rest]) -> test_defined(Rest);
-test_defined([]) -> false. \ No newline at end of file
+test_defined([]) -> false.
diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl
index 95b4624..9fee4e0 100644
--- a/src/rebar_app_info.erl
+++ b/src/rebar_app_info.erl
@@ -23,6 +23,7 @@
original_vsn/1,
original_vsn/2,
ebin_dir/1,
+ priv_dir/1,
applications/1,
applications/2,
profiles/1,
@@ -361,6 +362,10 @@ out_dir(AppInfo=#app_info_t{}, OutDir) ->
ebin_dir(#app_info_t{out_dir=OutDir}) ->
ec_cnv:to_list(filename:join(OutDir, "ebin")).
+-spec priv_dir(t()) -> file:name().
+priv_dir(#app_info_t{out_dir=OutDir}) ->
+ ec_cnv:to_list(filename:join(OutDir, "priv")).
+
-spec resource_type(t(), pkg | src) -> t().
resource_type(AppInfo=#app_info_t{}, Type) ->
AppInfo#app_info_t{resource_type=Type}.
diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl
index 602fd42..d3ef841 100644
--- a/src/rebar_app_utils.erl
+++ b/src/rebar_app_utils.erl
@@ -118,14 +118,14 @@ parse_dep(Dep, Parent, DepsDir, State, Locks, Level) ->
end.
parse_dep(Parent, {Name, Vsn, {pkg, PkgName}}, DepsDir, IsLock, State) ->
- {PkgName1, PkgVsn} = parse_goal(ec_cnv:to_binary(PkgName), ec_cnv:to_binary(Vsn)),
+ {PkgName1, PkgVsn} = {ec_cnv:to_binary(PkgName), ec_cnv:to_binary(Vsn)},
dep_to_app(Parent, DepsDir, Name, PkgVsn, {pkg, PkgName1, PkgVsn}, IsLock, State);
parse_dep(Parent, {Name, {pkg, PkgName}}, DepsDir, IsLock, State) ->
%% Package dependency with different package name from app name
dep_to_app(Parent, DepsDir, Name, undefined, {pkg, ec_cnv:to_binary(PkgName), undefined}, IsLock, State);
parse_dep(Parent, {Name, Vsn}, DepsDir, IsLock, State) when is_list(Vsn); is_binary(Vsn) ->
%% Versioned Package dependency
- {PkgName, PkgVsn} = parse_goal(ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)),
+ {PkgName, PkgVsn} = {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)},
dep_to_app(Parent, DepsDir, PkgName, PkgVsn, {pkg, PkgName, PkgVsn}, IsLock, State);
parse_dep(Parent, Name, DepsDir, IsLock, State) when is_atom(Name); is_binary(Name) ->
%% Unversioned package dependency
@@ -166,23 +166,26 @@ dep_to_app(Parent, DepsDir, Name, Vsn, Source, IsLock, State) ->
Overrides = rebar_state:get(State, overrides, []),
AppInfo2 = rebar_app_info:set(AppInfo1, overrides, rebar_app_info:get(AppInfo, overrides, [])++Overrides),
AppInfo3 = rebar_app_info:apply_overrides(rebar_app_info:get(AppInfo2, overrides, []), AppInfo2),
- rebar_app_info:is_lock(AppInfo3, IsLock).
+ AppInfo4 = rebar_app_info:apply_profiles(AppInfo3, [default, prod]),
+ AppInfo5 = rebar_app_info:profiles(AppInfo4, [default]),
+ rebar_app_info:is_lock(AppInfo5, IsLock).
-update_source(AppInfo, {pkg, PkgName, undefined}, State) ->
- {PkgName1, PkgVsn1} = get_package(PkgName, State),
+update_source(AppInfo, {pkg, PkgName, PkgVsn}, State) ->
+ {PkgName1, PkgVsn1} = case PkgVsn of
+ undefined ->
+ get_package(PkgName, "0", State);
+ <<"~>", Vsn/binary>> ->
+ [Vsn1] = binary:split(Vsn, [<<" ">>], [trim_all, global]),
+ get_package(PkgName, Vsn1, State);
+ _ ->
+ {PkgName, PkgVsn}
+ end,
AppInfo1 = rebar_app_info:source(AppInfo, {pkg, PkgName1, PkgVsn1}),
Deps = rebar_packages:deps(PkgName1
,PkgVsn1
,State),
AppInfo2 = rebar_app_info:resource_type(rebar_app_info:deps(AppInfo1, Deps), pkg),
rebar_app_info:original_vsn(AppInfo2, PkgVsn1);
-update_source(AppInfo, {pkg, PkgName, PkgVsn}, State) ->
- AppInfo1 = rebar_app_info:source(AppInfo, {pkg, PkgName, PkgVsn}),
- Deps = rebar_packages:deps(PkgName
- ,PkgVsn
- ,State),
- AppInfo2 = rebar_app_info:resource_type(rebar_app_info:deps(AppInfo1, Deps), pkg),
- rebar_app_info:original_vsn(AppInfo2, PkgVsn);
update_source(AppInfo, Source, _State) ->
rebar_app_info:source(AppInfo, Source).
@@ -198,19 +201,8 @@ format_error(Error) ->
%% Internal functions
%% ===================================================================
--spec parse_goal(binary(), binary()) -> {binary(), binary()} | {binary(), binary(), binary()}.
-parse_goal(Name, Constraint) ->
- case re:run(Constraint, "([^\\d]*)(\\d.*)", [{capture, [1,2], binary}]) of
- {match, [<<>>, Vsn]} ->
- {Name, Vsn};
- {match, [Op, Vsn]} ->
- {Name, Vsn, binary_to_atom(Op, utf8)};
- nomatch ->
- throw(?PRV_ERROR({bad_constraint, Name, Constraint}))
- end.
-
-get_package(Dep, State) ->
- case rebar_packages:find_highest_matching(Dep, "0", ?PACKAGE_TABLE, State) of
+get_package(Dep, Vsn, State) ->
+ case rebar_packages:find_highest_matching(Dep, Vsn, ?PACKAGE_TABLE, State) of
{ok, HighestDepVsn} ->
{Dep, HighestDepVsn};
none ->
diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl
index 09e3114..3729704 100644
--- a/src/rebar_dir.erl
+++ b/src/rebar_dir.erl
@@ -121,8 +121,37 @@ processing_base_dir(State, Dir) ->
AbsDir = filename:absname(Dir),
AbsDir =:= rebar_state:get(State, base_dir).
+make_absolute_path(Path) ->
+ case filename:pathtype(Path) of
+ absolute ->
+ Path;
+ relative ->
+ {ok, Dir} = file:get_cwd(),
+ filename:join([Dir, Path]);
+ volumerelative ->
+ Volume = hd(filename:split(Path)),
+ {ok, Dir} = file:get_cwd(Volume),
+ filename:join([Dir, Path])
+ end.
+
+make_normalized_path(Path) ->
+ AbsPath = make_absolute_path(Path),
+ Components = filename:split(AbsPath),
+ make_normalized_path(Components, []).
+
+make_normalized_path([], NormalizedPath) ->
+ filename:join(lists:reverse(NormalizedPath));
+make_normalized_path([H|T], NormalizedPath) ->
+ case H of
+ "." -> make_normalized_path(T, NormalizedPath);
+ ".." -> make_normalized_path(T, tl(NormalizedPath));
+ _ -> make_normalized_path(T, [H|NormalizedPath])
+ end.
+
make_relative_path(Source, Target) ->
- do_make_relative_path(filename:split(Source), filename:split(Target)).
+ AbsSource = make_normalized_path(Source),
+ AbsTarget = make_normalized_path(Target),
+ do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)).
do_make_relative_path([H|T1], [H|T2]) ->
do_make_relative_path(T1, T2);
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 57b7387..3480cf6 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -116,7 +116,7 @@ compile(AppInfo, CompileOpts) when element(1, AppInfo) == app_info_t ->
check_files([filename:join(Dir, File)
|| File <- rebar_opts:get(RebarOpts, mib_first_files, [])]),
filename:join(Dir, "mibs"), ".mib", filename:join([Dir, "priv", "mibs"]), ".bin",
- fun compile_mib/3),
+ compile_mib(AppInfo)),
SrcDirs = lists:map(fun(SrcDir) -> filename:join(Dir, SrcDir) end,
rebar_dir:src_dirs(RebarOpts, ["src"])),
@@ -304,7 +304,8 @@ needed_files(G, ErlOpts, Dir, OutDir, SourceFiles) ->
TargetBase = target_base(OutDir, Source),
Target = TargetBase ++ ".beam",
AllOpts = [{outdir, filename:dirname(Target)}
- ,{i, filename:join(Dir, "include")}] ++ ErlOpts,
+ ,{i, filename:join(Dir, "include")}
+ ,{i, Dir}] ++ ErlOpts,
digraph:vertex(G, Source) > {Source, filelib:last_modified(Target)}
orelse opts_changed(AllOpts, TargetBase)
end, SourceFiles).
@@ -503,7 +504,7 @@ internal_erl_compile(_Opts, Dir, Module, OutDir, ErlOpts) ->
Target = target_base(OutDir, Module) ++ ".beam",
ok = filelib:ensure_dir(Target),
AllOpts = [{outdir, filename:dirname(Target)}] ++ ErlOpts ++
- [{i, filename:join(Dir, "include")}, return],
+ [{i, filename:join(Dir, "include")}, {i, Dir}, return],
case compile:file(Module, AllOpts) of
{ok, _Mod} ->
ok;
@@ -516,32 +517,38 @@ internal_erl_compile(_Opts, Dir, Module, OutDir, ErlOpts) ->
target_base(OutDir, Source) ->
filename:join(OutDir, filename:basename(Source, ".erl")).
--spec compile_mib(file:filename(), file:filename(),
- rebar_dict()) -> 'ok'.
-compile_mib(Source, Target, Opts) ->
- Dir = filename:dirname(Target),
- ok = filelib:ensure_dir(Target),
- ok = filelib:ensure_dir(filename:join([Dir, "include", "dummy.hrl"])),
- AllOpts = [{outdir, Dir}
- ,{i, [Dir]}] ++
- rebar_opts:get(Opts, mib_opts, []),
-
- case snmpc:compile(Source, AllOpts) of
- {ok, _} ->
- Mib = filename:rootname(Target),
- MibToHrlOpts =
- case proplists:get_value(verbosity, AllOpts, undefined) of
- undefined ->
- #options{specific = []};
- Verbosity ->
- #options{specific = [{verbosity, Verbosity}]}
- end,
- ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
- Hrl_filename = Mib ++ ".hrl",
- rebar_file_utils:mv(Hrl_filename, "include"),
- ok;
- {error, compilation_failed} ->
- ?FAIL
+-spec compile_mib(rebar_app_info:t()) ->
+ fun((file:filename(), file:filename(), rebar_dict()) -> 'ok').
+compile_mib(AppInfo) ->
+ fun(Source, Target, Opts) ->
+ Dir = filename:dirname(Target),
+ Mib = filename:rootname(Target),
+ HrlFilename = Mib ++ ".hrl",
+
+ AppInclude = filename:join([rebar_app_info:dir(AppInfo), "include"]),
+
+ ok = filelib:ensure_dir(Target),
+ ok = filelib:ensure_dir(filename:join([AppInclude, "dummy.hrl"])),
+
+ AllOpts = [{outdir, Dir}
+ ,{i, [Dir]}] ++
+ rebar_opts:get(Opts, mib_opts, []),
+
+ case snmpc:compile(Source, AllOpts) of
+ {ok, _} ->
+ MibToHrlOpts =
+ case proplists:get_value(verbosity, AllOpts, undefined) of
+ undefined ->
+ #options{specific = []};
+ Verbosity ->
+ #options{specific = [{verbosity, Verbosity}]}
+ end,
+ ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
+ rebar_file_utils:mv(HrlFilename, AppInclude),
+ ok;
+ {error, compilation_failed} ->
+ ?FAIL
+ end
end.
-spec compile_xrl(file:filename(), file:filename(),
@@ -688,7 +695,7 @@ warn_and_find_path(File, Dir) ->
true ->
[SrcHeader];
false ->
- IncludeDir = filename:join(filename:join(rebar_utils:droplast(filename:split(Dir))), "include"),
+ IncludeDir = filename:join(rebar_utils:droplast(filename:split(Dir))++["include"]),
IncludeHeader = filename:join(IncludeDir, File),
case filelib:is_regular(IncludeHeader) of
true ->
diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl
index ea1a6a2..0f84520 100644
--- a/src/rebar_file_utils.erl
+++ b/src/rebar_file_utils.erl
@@ -139,7 +139,7 @@ cp_r(Sources, Dest) ->
{unix, _} ->
EscSources = [rebar_utils:escape_chars(Src) || Src <- Sources],
SourceStr = string:join(EscSources, " "),
- {ok, []} = rebar_utils:sh(?FMT("cp -R ~s \"~s\"",
+ {ok, []} = rebar_utils:sh(?FMT("cp -Rp ~s \"~s\"",
[SourceStr, rebar_utils:escape_double_quotes(Dest)]),
[{use_stdout, false}, abort_on_error]),
ok;
@@ -262,9 +262,11 @@ path_from_ancestor_(_, _) -> {error, badparent}.
%% reduce a filepath by removing all incidences of `.' and `..'
-spec canonical_path(string()) -> string().
-canonical_path(Dir) -> canonical_path([], filename:split(filename:absname(Dir))).
+canonical_path(Dir) ->
+ Canon = canonical_path([], filename:split(filename:absname(Dir))),
+ filename:nativename(Canon).
-canonical_path([], []) -> filename:nativename("/");
+canonical_path([], []) -> filename:absname("/");
canonical_path(Acc, []) -> filename:join(lists:reverse(Acc));
canonical_path(Acc, ["."|Rest]) -> canonical_path(Acc, Rest);
canonical_path([_|Acc], [".."|Rest]) -> canonical_path(Acc, Rest);
@@ -283,13 +285,19 @@ delete_each_dir_win32([Dir | Rest]) ->
delete_each_dir_win32(Rest).
xcopy_win32(Source,Dest)->
- %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to
+ %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Changed to robocopy to
%% handle long names. May have issues with older windows.
Cmd = case filelib:is_dir(Source) of
true ->
+ %% For robocopy, copying /a/b/c/ to /d/e/f/ recursively does not
+ %% create /d/e/f/c/*, but rather copies all files to /d/e/f/*.
+ %% The usage we make here expects the former, not the later, so we
+ %% must manually add the last fragment of a directory to the `Dest`
+ %% in order to properly replicate POSIX platforms
+ NewDest = filename:join([Dest, filename:basename(Source)]),
?FMT("robocopy \"~s\" \"~s\" /e /is 1> nul",
[rebar_utils:escape_double_quotes(filename:nativename(Source)),
- rebar_utils:escape_double_quotes(filename:nativename(Dest))]);
+ rebar_utils:escape_double_quotes(filename:nativename(NewDest))]);
false ->
?FMT("robocopy \"~s\" \"~s\" \"~s\" /e /is 1> nul",
[rebar_utils:escape_double_quotes(filename:nativename(filename:dirname(Source))),
diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl
index bea74a2..876d047 100644
--- a/src/rebar_git_resource.erl
+++ b/src/rebar_git_resource.erl
@@ -45,7 +45,7 @@ needs_update(Dir, {git, Url, {branch, Branch}}) ->
not ((Current =:= []) andalso compare_url(Dir, Url));
needs_update(Dir, {git, Url, "master"}) ->
needs_update(Dir, {git, Url, {branch, "master"}});
-needs_update(Dir, {git, Url, Ref}) ->
+needs_update(Dir, {git, _, Ref}) ->
{ok, Current} = rebar_utils:sh(?FMT("git rev-parse -q HEAD", []),
[{cd, Dir}]),
Current1 = string:strip(string:strip(Current, both, $\n), both, $\r),
@@ -64,7 +64,7 @@ needs_update(Dir, {git, Url, Ref}) ->
end,
?DEBUG("Comparing git ref ~s with ~s", [Ref1, Current1]),
- not ((Current1 =:= Ref2) andalso compare_url(Dir, Url)).
+ (Current1 =/= Ref2).
compare_url(Dir, Url) ->
{ok, CurrentUrl} = rebar_utils:sh(?FMT("git config --get remote.origin.url", []),
diff --git a/src/rebar_opts.erl b/src/rebar_opts.erl
index 47451c5..b02a504 100644
--- a/src/rebar_opts.erl
+++ b/src/rebar_opts.erl
@@ -111,6 +111,12 @@ merge_opts(NewOpts, OldOpts) ->
NewValue;
(profiles, NewValue, OldValue) ->
dict:to_list(merge_opts(dict:from_list(NewValue), dict:from_list(OldValue)));
+ (mib_first_files, Value, Value) ->
+ Value;
+ (mib_first_files, NewValue, OldValue) ->
+ OldValue ++ NewValue;
+ (relx, NewValue, OldValue) ->
+ rebar_utils:tup_umerge(OldValue, NewValue);
(_Key, NewValue, OldValue) when is_list(NewValue) ->
case io_lib:printable_list(NewValue) of
true when NewValue =:= [] ->
diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl
index 7be3372..c56009e 100644
--- a/src/rebar_packages.erl
+++ b/src/rebar_packages.erl
@@ -28,7 +28,17 @@ packages(State) ->
ok;
false ->
?DEBUG("Error loading package index.", []),
- ?ERROR("Bad packages index, try to fix with `rebar3 update`", []),
+ handle_bad_index(State)
+ end.
+
+handle_bad_index(State) ->
+ ?ERROR("Bad packages index. Trying to fix by updating the registry.", []),
+ {ok, State1} = rebar_prv_update:do(State),
+ case load_and_verify_version(State1) of
+ true ->
+ ok;
+ false ->
+ %% Still unable to load after an update, create an empty registry
ets:new(?PACKAGE_TABLE, [named_table, public])
end.
@@ -36,7 +46,7 @@ close_packages() ->
catch ets:delete(?PACKAGE_TABLE).
load_and_verify_version(State) ->
- RegistryDir = registry_dir(State),
+ {ok, RegistryDir} = registry_dir(State),
case ets:file2tab(filename:join(RegistryDir, ?INDEX_FILE)) of
{ok, _} ->
case ets:lookup_element(?PACKAGE_TABLE, package_index_version, 2) of
@@ -52,10 +62,24 @@ load_and_verify_version(State) ->
deps(Name, Vsn, State) ->
try
- ?MODULE:verify_table(State),
- ets:lookup_element(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, 2)
+ deps_(Name, Vsn, State)
+ catch
+ _:_ ->
+ handle_missing_package(Name, Vsn, State)
+ end.
+
+deps_(Name, Vsn, State) ->
+ ?MODULE:verify_table(State),
+ ets:lookup_element(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, 2).
+
+handle_missing_package(Name, Vsn, State) ->
+ ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]),
+ {ok, State1} = rebar_prv_update:do(State),
+ try
+ deps_(Name, Vsn, State1)
catch
_:_ ->
+ %% Even after an update the package is still missing, time to error out
throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}))
end.
@@ -65,21 +89,30 @@ registry_dir(State) ->
?DEFAULT_CDN ->
RegistryDir = filename:join([CacheDir, "hex", "default"]),
ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
- RegistryDir;
+ {ok, RegistryDir};
CDN ->
- {ok, {_, _, Host, _, Path, _}} = http_uri:parse(CDN),
- CDNHostPath = lists:reverse(string:tokens(Host, ".")),
- CDNPath = tl(filename:split(Path)),
- RegistryDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath),
- ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
- RegistryDir
+ case rebar_utils:url_append_path(CDN, ?REMOTE_PACKAGE_DIR) of
+ {ok, Parsed} ->
+ {ok, {_, _, Host, _, Path, _}} = http_uri:parse(Parsed),
+ CDNHostPath = lists:reverse(string:tokens(Host, ".")),
+ CDNPath = tl(filename:split(Path)),
+ RegistryDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath),
+ ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
+ {ok, RegistryDir};
+ _ ->
+ {uri_parse_error, CDN}
+ end
end.
package_dir(State) ->
- RegistryDir = registry_dir(State),
- PackageDir = filename:join([RegistryDir, "packages"]),
- ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
- PackageDir.
+ case registry_dir(State) of
+ {ok, RegistryDir} ->
+ PackageDir = filename:join([RegistryDir, "packages"]),
+ ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
+ {ok, PackageDir};
+ Error ->
+ Error
+ end.
registry_checksum({pkg, Name, Vsn}, State) ->
try
@@ -138,12 +171,12 @@ handle_single_vsn(Dep, Vsn, Constraint) ->
{ok, Vsn};
false ->
?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
- "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]),
+ "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]),
{ok, Vsn}
end.
format_error({missing_package, Package, Version}) ->
- io_lib:format("Package not found in registry: ~s-~s. Try to fix with `rebar3 update`", [Package, Version]).
+ io_lib:format("Package not found in registry: ~s-~s.", [Package, Version]).
verify_table(State) ->
ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State).
diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl
index 4f55ad1..ec7e09d 100644
--- a/src/rebar_pkg_resource.erl
+++ b/src/rebar_pkg_resource.erl
@@ -30,11 +30,15 @@ needs_update(Dir, {pkg, _Name, Vsn}) ->
download(TmpDir, Pkg={pkg, Name, Vsn}, State) ->
CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
- PackageDir = rebar_packages:package_dir(State),
+ {ok, PackageDir} = rebar_packages:package_dir(State),
Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
CachePath = filename:join(PackageDir, Package),
- Url = string:join([CDN, Package], "/"),
- cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State).
+ case rebar_utils:url_append_path(CDN, filename:join(?REMOTE_PACKAGE_DIR, Package)) of
+ {ok, Url} ->
+ cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State);
+ _ ->
+ {fetch_fail, Name, Vsn}
+ end.
cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn}, Url, ETag, State) ->
case request(Url, ETag) of
@@ -100,7 +104,7 @@ make_vsn(_) ->
{error, "Replacing version of type pkg not supported."}.
request(Url, ETag) ->
- case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]},
+ case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]++[{"User-Agent", rebar_utils:user_agent()}]},
[{ssl, ssl_opts(Url)}, {relaxed, true}],
[{body_format, binary}],
rebar) of
diff --git a/src/rebar_plugins.erl b/src/rebar_plugins.erl
index f2d3977..3c33498 100644
--- a/src/rebar_plugins.erl
+++ b/src/rebar_plugins.erl
@@ -3,7 +3,8 @@
-module(rebar_plugins).
--export([project_apps_install/1
+-export([top_level_install/1
+ ,project_apps_install/1
,install/2
,handle_plugins/3
,handle_plugins/4]).
@@ -14,11 +15,18 @@
%% Public API
%% ===================================================================
+-spec top_level_install(rebar_state:t()) -> rebar_state:t().
+top_level_install(State) ->
+ 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).
+
-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),
@@ -34,10 +42,20 @@ project_apps_install(State) ->
-spec install(rebar_state:t(), rebar_app_info:t()) -> rebar_state:t().
install(State, AppInfo) ->
Profiles = rebar_state:current_profiles(State),
- lists:foldl(fun(Profile, StateAcc) ->
- Plugins = rebar_app_info:get(AppInfo, {plugins, Profile}, []),
- handle_plugins(Profile, Plugins, StateAcc)
- end, State, Profiles).
+
+ %% don't lose the overrides of the dep we are processing plugins for
+ Overrides = rebar_app_info:get(AppInfo, overrides, []),
+ StateOverrides = rebar_state:get(State, overrides, []),
+ AllOverrides = Overrides ++ StateOverrides,
+ State1 = rebar_state:set(State, overrides, AllOverrides),
+
+ State2 = lists:foldl(fun(Profile, StateAcc) ->
+ Plugins = rebar_app_info:get(AppInfo, {plugins, Profile}, []),
+ handle_plugins(Profile, Plugins, StateAcc)
+ end, State1, Profiles),
+
+ %% Reset the overrides after processing the dep
+ rebar_state:set(State2, overrides, StateOverrides).
handle_plugins(Profile, Plugins, State) ->
handle_plugins(Profile, Plugins, State, false).
diff --git a/src/rebar_prv_app_discovery.erl b/src/rebar_prv_app_discovery.erl
index 5449f82..1954214 100644
--- a/src/rebar_prv_app_discovery.erl
+++ b/src/rebar_prv_app_discovery.erl
@@ -36,7 +36,8 @@ do(State) ->
LibDirs = rebar_dir:lib_dirs(State),
try
State1 = rebar_app_discover:do(State, LibDirs),
- {ok, State1}
+ State2 = rebar_plugins:project_apps_install(State1),
+ {ok, State2}
catch
throw:{error, {rebar_packages, Error}} ->
{error, {rebar_packages, Error}};
diff --git a/src/rebar_prv_clean.erl b/src/rebar_prv_clean.erl
index 7f952e3..8f31fdd 100644
--- a/src/rebar_prv_clean.erl
+++ b/src/rebar_prv_clean.erl
@@ -27,32 +27,35 @@ init(State) ->
{example, "rebar3 clean"},
{short_desc, "Remove compiled beam files from apps."},
{desc, "Remove compiled beam files from apps."},
- {opts, [{all, $a, "all", undefined, "Clean all apps include deps"}]}])),
+ {opts, [{all, $a, "all", undefined, "Clean all apps include deps"},
+ {profile, $p, "profile", string, "Clean under profile. Equivalent to `rebar3 as <profile> clean`"}]}])),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Providers = rebar_state:providers(State),
- {all, All} = handle_args(State),
+ {All, Profiles} = handle_args(State),
+
+ State1 = rebar_state:apply_profiles(State, [list_to_atom(X) || X <- Profiles]),
Cwd = rebar_dir:get_cwd(),
- rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
+ rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State1),
case All of
true ->
- DepsDir = rebar_dir:deps_dir(State),
+ DepsDir = rebar_dir:deps_dir(State1),
AllApps = rebar_app_discover:find_apps([filename:join(DepsDir, "*")], all),
- clean_apps(State, Providers, AllApps);
+ clean_apps(State1, Providers, AllApps);
false ->
- ProjectApps = rebar_state:project_apps(State),
- clean_apps(State, Providers, ProjectApps)
+ ProjectApps = rebar_state:project_apps(State1),
+ clean_apps(State1, Providers, ProjectApps)
end,
- clean_extras(State),
+ clean_extras(State1),
- rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State),
+ rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1),
- {ok, State}.
+ {ok, State1}.
-spec format_error(any()) -> iolist().
format_error(Reason) ->
@@ -78,4 +81,5 @@ clean_extras(State) ->
handle_args(State) ->
{Args, _} = rebar_state:command_parsed_args(State),
All = proplists:get_value(all, Args, false),
- {all, All}.
+ Profiles = proplists:get_all_values(profile, Args),
+ {All, Profiles}.
diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl
index 1f4c02d..4be50d8 100644
--- a/src/rebar_prv_common_test.erl
+++ b/src/rebar_prv_common_test.erl
@@ -2,19 +2,21 @@
%% ex: ts=4 sw=4 et
-module(rebar_prv_common_test).
+
-behaviour(provider).
-export([init/1,
do/1,
format_error/1]).
%% exported for test purposes, consider private
--export([setup_ct/1]).
+-export([compile/2, prepare_tests/1, translate_paths/2]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
-define(PROVIDER, ct).
--define(DEPS, [compile]).
+%% we need to modify app_info state before compile
+-define(DEPS, [lock]).
%% ===================================================================
%% Public API
@@ -31,77 +33,461 @@ init(State) ->
{desc, "Run Common Tests."},
{opts, ct_opts(State)},
{profiles, [test]}]),
- State1 = rebar_state:add_provider(State, Provider),
- State2 = rebar_state:add_to_profile(State1, test, test_state(State1)),
- {ok, State2}.
+ {ok, rebar_state:add_provider(State, Provider)}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
+ Tests = prepare_tests(State),
+ case compile(State, Tests) of
+ %% successfully compiled apps
+ {ok, S} -> do(S, Tests);
+ %% this should look like a compiler error, not a ct error
+ Error -> Error
+ end.
+
+do(State, Tests) ->
?INFO("Running Common Test suites...", []),
- rebar_utils:update_code(rebar_state:code_paths(State, all_deps)),
+ rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]),
%% Run ct provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
- try run_test(State) of
- {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(State, default)),
- Result;
- ?PRV_ERROR(_) = Error ->
+ case Tests of
+ {ok, T} ->
+ case run_tests(State, T) of
+ ok ->
+ %% Run ct provider posthooks
+ rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State),
+ rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
+ {ok, State};
+ Error ->
+ rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
+ Error
+ end;
+ Error ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
Error
- catch
- throw:{error, Reason} ->
- rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
- ?PRV_ERROR(Reason)
end.
+run_tests(State, Opts) ->
+ T = translate_paths(State, Opts),
+ Opts1 = setup_logdir(State, T),
+ Opts2 = turn_off_auto_compile(Opts1),
+ ?DEBUG("ct_opts ~p", [Opts2]),
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ Result = case proplists:get_value(verbose, RawOpts, false) of
+ true -> run_test_verbose(Opts2);
+ false -> run_test_quiet(Opts2)
+ end,
+ ok = maybe_write_coverdata(State),
+ Result.
+
-spec format_error(any()) -> iolist().
-format_error({multiple_dirs_and_suites, Opts}) ->
- io_lib:format("Multiple dirs declared alongside suite in opts: ~p", [Opts]);
-format_error({bad_dir_or_suite, Opts}) ->
- io_lib:format("Bad value for dir or suite in opts: ~p", [Opts]);
+format_error({error, Reason}) ->
+ io_lib:format("Error running tests:~n ~p", [Reason]);
+format_error({error_running_tests, Reason}) ->
+ format_error({error, Reason});
format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]);
-format_error({error_running_tests, Reason}) ->
- io_lib:format("Error running tests: ~p", [Reason]);
-format_error(suite_at_project_root) ->
- io_lib:format("Test suites can't be located in project root", []);
-format_error({error, Reason}) ->
- io_lib:format("Unknown error: ~p", [Reason]).
+format_error({badconfig, {Msg, {Value, Key}}}) ->
+ io_lib:format(Msg, [Value, Key]);
+format_error({badconfig, Msg}) ->
+ io_lib:format(Msg, []);
+format_error({multiple_errors, Errors}) ->
+ io_lib:format(lists:concat(["Error running tests:"] ++
+ lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []).
%% ===================================================================
%% Internal functions
%% ===================================================================
-run_test(State) ->
- case setup_ct(State) of
- {error, {no_tests_specified, Opts}} ->
- ?WARN("No tests specified in opts: ~p", [Opts]),
- {ok, State};
- Opts ->
- Opts1 = setup_logdir(State, Opts),
- ?DEBUG("common test opts: ~p", [Opts1]),
- run_test(State, Opts1)
- end.
+prepare_tests(State) ->
+ %% command line test options
+ CmdOpts = cmdopts(State),
+ %% rebar.config test options
+ CfgOpts = cfgopts(State),
+ ProjectApps = rebar_state:project_apps(State),
+ %% prioritize tests to run first trying any command line specified
+ %% tests falling back to tests specified in the config file finally
+ %% running a default set if no other tests are present
+ select_tests(State, ProjectApps, CmdOpts, CfgOpts).
-run_test(State, Opts) ->
+cmdopts(State) ->
{RawOpts, _} = rebar_state:command_parsed_args(State),
- ok = rebar_prv_cover:maybe_cover_compile(State, apps),
- Result = case proplists:get_value(verbose, RawOpts, false) of
- true -> run_test_verbose(Opts);
- false -> run_test_quiet(Opts)
+ %% filter out opts common_test doesn't know about and convert
+ %% to ct acceptable forms
+ transform_opts(RawOpts, []).
+
+transform_opts([], Acc) -> lists:reverse(Acc);
+transform_opts([{dir, Dirs}|Rest], Acc) ->
+ transform_opts(Rest, [{dir, split_string(Dirs)}|Acc]);
+transform_opts([{suite, Suites}|Rest], Acc) ->
+ transform_opts(Rest, [{suite, split_string(Suites)}|Acc]);
+transform_opts([{group, Groups}|Rest], Acc) ->
+ transform_opts(Rest, [{group, split_string(Groups)}|Acc]);
+transform_opts([{testcase, Cases}|Rest], Acc) ->
+ transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]);
+transform_opts([{config, Configs}|Rest], Acc) ->
+ transform_opts(Rest, [{config, split_string(Configs)}|Acc]);
+transform_opts([{logopts, LogOpts}|Rest], Acc) ->
+ transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]);
+transform_opts([{force_stop, "true"}|Rest], Acc) ->
+ transform_opts(Rest, [{force_stop, true}|Acc]);
+transform_opts([{force_stop, "false"}|Rest], Acc) ->
+ transform_opts(Rest, [{force_stop, false}|Acc]);
+transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
+ transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
+transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
+ transform_opts(Rest, [{create_priv_dir, list_to_atom(CreatePrivDir)}|Acc]);
+%% drop cover from opts, ct doesn't care about it
+transform_opts([{cover, _}|Rest], Acc) ->
+ transform_opts(Rest, Acc);
+%% drop verbose from opts, ct doesn't care about it
+transform_opts([{verbose, _}|Rest], Acc) ->
+ transform_opts(Rest, Acc);
+%% getopt should handle anything else
+transform_opts([Opt|Rest], Acc) ->
+ transform_opts(Rest, [Opt|Acc]).
+
+split_string(String) ->
+ string:tokens(String, [$,]).
+
+cfgopts(State) ->
+ case rebar_state:get(State, ct_opts, []) of
+ Opts when is_list(Opts) ->
+ ensure_opts(add_hooks(Opts, State), []);
+ Wrong ->
+ %% probably a single non list term
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, ct_opts}}})
+ end.
+
+ensure_opts([], Acc) -> lists:reverse(Acc);
+ensure_opts([{test_spec, _}|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, "Test specs not supported"});
+ensure_opts([{auto_compile, _}|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, "Auto compile not supported"});
+ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
+ ensure_opts(Rest, [{suite, Suite}|Acc]);
+ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) ->
+ ensure_opts(Rest, [{suite, atom_to_list(Suite)}|Acc]);
+ensure_opts([{suite, Suites}|Rest], Acc) ->
+ NewSuites = {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
+ (S) when is_list(S) -> S
+ end,
+ Suites)},
+ ensure_opts(Rest, [NewSuites|Acc]);
+ensure_opts([{K, V}|Rest], Acc) ->
+ ensure_opts(Rest, [{K, V}|Acc]);
+ensure_opts([V|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, {"Member `~p' of option `~p' must be a 2-tuple", {V, ct_opts}}}).
+
+add_hooks(Opts, State) ->
+ case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of
+ {false, _} ->
+ Opts;
+ {true, false} ->
+ [{ct_hooks, [cth_readable_failonly, cth_readable_shell]} | Opts];
+ {true, {ct_hooks, Hooks}} ->
+ %% Make sure hooks are there once only.
+ ReadableHooks = [cth_readable_failonly, cth_readable_shell],
+ NewHooks = (Hooks -- ReadableHooks) ++ ReadableHooks,
+ lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks})
+ end.
+
+select_tests(_, _, {error, _} = Error, _) -> Error;
+select_tests(_, _, _, {error, _} = Error) -> Error;
+select_tests(State, ProjectApps, CmdOpts, CfgOpts) ->
+ Merged = lists:ukeymerge(1,
+ lists:ukeysort(1, CmdOpts),
+ lists:ukeysort(1, CfgOpts)),
+ %% make sure `dir` and/or `suite` from command line go in as
+ %% a pair overriding both `dir` and `suite` from config if
+ %% they exist
+ Opts = case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of
+ {undefined, undefined} -> Merged;
+ {_Suite, undefined} -> lists:keydelete(dir, 1, Merged);
+ {undefined, _Dir} -> lists:keydelete(suite, 1, Merged);
+ {_Suite, _Dir} -> Merged
end,
- ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
- case Result of
- ok -> {ok, State};
- Error -> Error
+ discover_tests(State, ProjectApps, Opts).
+
+discover_tests(State, ProjectApps, Opts) ->
+ case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
+ %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test`
+ %% as suites
+ {undefined, undefined} -> {ok, [default_tests(State, ProjectApps)|Opts]};
+ {_, _} -> {ok, Opts}
+ end.
+
+default_tests(State, ProjectApps) ->
+ BareTest = filename:join([rebar_state:dir(State), "test"]),
+ F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
+ AppTests = application_dirs(ProjectApps, []),
+ case filelib:is_dir(BareTest) andalso not lists:any(F, ProjectApps) of
+ %% `test` dir at root of project is already scheduled to be
+ %% included or `test` does not exist
+ false -> {dir, AppTests};
+ %% need to add `test` dir at root to dirs to be included
+ true -> {dir, AppTests ++ [BareTest]}
+ end.
+
+application_dirs([], []) -> [];
+application_dirs([], Acc) -> lists:reverse(Acc);
+application_dirs([App|Rest], Acc) ->
+ TestDir = filename:join([rebar_app_info:dir(App), "test"]),
+ case filelib:is_dir(TestDir) of
+ true -> application_dirs(Rest, [TestDir|Acc]);
+ false -> application_dirs(Rest, Acc)
+ end.
+
+compile(State, {ok, _} = Tests) ->
+ %% inject `ct_first_files` and `ct_compile_opts` into the applications
+ %% to be compiled
+ case inject_ct_state(State, Tests) of
+ {ok, NewState} -> do_compile(NewState);
+ Error -> Error
+ end;
+%% maybe compile even in the face of errors?
+compile(_State, Error) -> Error.
+
+do_compile(State) ->
+ case rebar_prv_compile:do(State) of
+ %% successfully compiled apps
+ {ok, S} ->
+ ok = maybe_cover_compile(S),
+ {ok, S};
+ %% this should look like a compiler error, not an eunit error
+ Error -> Error
+ end.
+
+inject_ct_state(State, {ok, Tests}) ->
+ Apps = rebar_state:project_apps(State),
+ case inject_ct_state(State, Apps, []) of
+ {ok, {NewState, ModdedApps}} ->
+ test_dirs(NewState, ModdedApps, Tests);
+ {error, _} = Error -> Error
+ end;
+inject_ct_state(_State, Error) -> Error.
+
+inject_ct_state(State, [App|Rest], Acc) ->
+ case inject(rebar_app_info:opts(App), State) of
+ {error, _} = Error -> Error;
+ NewOpts ->
+ NewApp = rebar_app_info:opts(App, NewOpts),
+ inject_ct_state(State, Rest, [NewApp|Acc])
+ end;
+inject_ct_state(State, [], Acc) ->
+ case inject(rebar_state:opts(State), State) of
+ {error, _} = Error -> Error;
+ NewOpts ->
+ {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
+ end.
+
+opts(Opts, Key, Default) ->
+ case rebar_opts:get(Opts, Key, Default) of
+ Vs when is_list(Vs) -> Vs;
+ Wrong ->
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
+ end.
+
+inject(Opts, State) -> erl_opts(Opts, State).
+
+erl_opts(Opts, State) ->
+ %% append `ct_compile_opts` to app defined `erl_opts`
+ ErlOpts = opts(Opts, erl_opts, []),
+ CTOpts = opts(Opts, ct_compile_opts, []),
+ case add_transforms(append(CTOpts, ErlOpts), State) of
+ {error, Error} -> {error, Error};
+ NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts))
+ end.
+
+first_files(Opts) ->
+ %% append `ct_first_files` to app defined `erl_first_files`
+ FirstFiles = opts(Opts, erl_first_files, []),
+ CTFirstFiles = opts(Opts, ct_first_files, []),
+ case append(CTFirstFiles, FirstFiles) of
+ {error, _} = Error -> Error;
+ NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles)
+ end.
+
+append({error, _} = Error, _) -> Error;
+append(_, {error, _} = Error) -> Error;
+append(A, B) -> A ++ B.
+
+add_transforms(CTOpts, State) when is_list(CTOpts) ->
+ case readable(State) of
+ true ->
+ ReadableTransform = [{parse_transform, cth_readable_transform}],
+ (CTOpts -- ReadableTransform) ++ ReadableTransform;
+ false ->
+ CTOpts
+ end;
+add_transforms({error, _} = Error, _State) -> Error.
+
+readable(State) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ case proplists:get_value(readable, RawOpts) of
+ true -> true;
+ false -> false;
+ undefined -> rebar_state:get(State, ct_readable, true)
+ end.
+
+test_dirs(State, Apps, Opts) ->
+ case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
+ {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites});
+ {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs});
+ {Suites, Dir} when is_integer(hd(Dir)) ->
+ set_compile_dirs(State, Apps, join(Suites, Dir));
+ {Suites, [Dir]} when is_integer(hd(Dir)) ->
+ set_compile_dirs(State, Apps, join(Suites, Dir));
+ {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"}
+ end.
+
+join(Suite, Dir) when is_integer(hd(Suite)) ->
+ {suite, [filename:join([Dir, Suite])]};
+join(Suites, Dir) ->
+ {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}.
+
+set_compile_dirs(State, Apps, {dir, Dir}) when is_integer(hd(Dir)) ->
+ %% single directory
+ %% insert `Dir` into an app if relative, or the base state if not
+ %% app relative but relative to the root or not at all if outside
+ %% project scope
+ {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
+ {ok, rebar_state:project_apps(NewState, NewApps)};
+set_compile_dirs(State, Apps, {dir, Dirs}) ->
+ %% multiple directories
+ F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
+ {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
+ {ok, rebar_state:project_apps(NewState, NewApps)};
+set_compile_dirs(State, Apps, {suite, Suites}) ->
+ %% suites with dir component
+ Dirs = find_suite_dirs(Suites),
+ F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
+ {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
+ {ok, rebar_state:project_apps(NewState, NewApps)}.
+
+find_suite_dirs(Suites) ->
+ AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites),
+ %% eliminate duplicates
+ lists:usort(AllDirs).
+
+maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
+ {ok, []} ->
+ %% normal operation involves copying the entire directory a
+ %% suite exists in but if the suite is in the app root directory
+ %% the current compiler tries to compile all subdirs including priv
+ %% instead copy only files ending in `.erl' and directories
+ %% ending in `_SUITE_data' into the `_build/PROFILE/lib/APP' dir
+ ok = copy_bare_suites(Dir, rebar_app_info:out_dir(App)),
+ Opts = inject_test_dir(rebar_state:opts(State), rebar_app_info:out_dir(App)),
+ {rebar_state:opts(State, Opts), AppAcc ++ [App]};
+ {ok, Path} ->
+ Opts = inject_test_dir(rebar_app_info:opts(App), Path),
+ {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
+ {error, badparent} ->
+ maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
+ end;
+maybe_inject_test_dir(State, AppAcc, [], Dir) ->
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
+ {ok, []} ->
+ %% normal operation involves copying the entire directory a
+ %% suite exists in but if the suite is in the root directory
+ %% that results in a loop as we copy `_build' into itself
+ %% instead copy only files ending in `.erl' and directories
+ %% ending in `_SUITE_data' in the `_build/PROFILE/extras' dir
+ ExtrasDir = filename:join([rebar_dir:base_dir(State), "extras"]),
+ ok = copy_bare_suites(Dir, ExtrasDir),
+ Opts = inject_test_dir(rebar_state:opts(State), ExtrasDir),
+ {rebar_state:opts(State, Opts), AppAcc};
+ {ok, Path} ->
+ Opts = inject_test_dir(rebar_state:opts(State), Path),
+ {rebar_state:opts(State, Opts), AppAcc};
+ {error, badparent} ->
+ {State, AppAcc}
+ end.
+
+copy_bare_suites(From, To) ->
+ filelib:ensure_dir(filename:join([To, "dummy.txt"])),
+ SrcFiles = rebar_utils:find_files(From, ".*\\.[e|h]rl\$", false),
+ DataDirs = lists:filter(fun filelib:is_dir/1,
+ filelib:wildcard(filename:join([From, "*_SUITE_data"]))),
+ ok = rebar_file_utils:cp_r(SrcFiles, To),
+ rebar_file_utils:cp_r(DataDirs, To).
+
+inject_test_dir(Opts, Dir) ->
+ %% append specified test targets to app defined `extra_src_dirs`
+ ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
+ rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
+
+translate_paths(State, Opts) ->
+ case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
+ {_Suites, undefined} -> translate_suites(State, Opts, []);
+ {undefined, _Dirs} -> translate_dirs(State, Opts, []);
+ %% both dirs and suites are defined, only translate dir paths
+ _ -> translate_dirs(State, Opts, [])
+ end.
+
+translate_dirs(_State, [], Acc) -> lists:reverse(Acc);
+translate_dirs(State, [{dir, Dir}|Rest], Acc) when is_integer(hd(Dir)) ->
+ %% single dir
+ Apps = rebar_state:project_apps(State),
+ translate_dirs(State, Rest, [{dir, translate(State, Apps, Dir)}|Acc]);
+translate_dirs(State, [{dir, Dirs}|Rest], Acc) ->
+ %% multiple dirs
+ Apps = rebar_state:project_apps(State),
+ NewDirs = {dir, lists:map(fun(Dir) -> translate(State, Apps, Dir) end, Dirs)},
+ translate_dirs(State, Rest, [NewDirs|Acc]);
+translate_dirs(State, [Test|Rest], Acc) ->
+ translate_dirs(State, Rest, [Test|Acc]).
+
+translate_suites(_State, [], Acc) -> lists:reverse(Acc);
+translate_suites(State, [{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
+ %% single suite
+ Apps = rebar_state:project_apps(State),
+ translate_suites(State, Rest, [{suite, translate_suite(State, Apps, Suite)}|Acc]);
+translate_suites(State, [{suite, Suites}|Rest], Acc) ->
+ %% multiple suites
+ Apps = rebar_state:project_apps(State),
+ NewSuites = {suite, lists:map(fun(Suite) -> translate_suite(State, Apps, Suite) end, Suites)},
+ translate_suites(State, Rest, [NewSuites|Acc]);
+translate_suites(State, [Test|Rest], Acc) ->
+ translate_suites(State, Rest, [Test|Acc]).
+
+translate_suite(State, Apps, Suite) ->
+ Dirname = filename:dirname(Suite),
+ Basename = filename:basename(Suite),
+ case Dirname of
+ "." -> Suite;
+ _ -> filename:join([translate(State, Apps, Dirname), Basename])
end.
+translate(State, [App|Rest], Path) ->
+ case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of
+ {ok, P} -> filename:join([rebar_app_info:out_dir(App), P]);
+ {error, badparent} -> translate(State, Rest, Path)
+ end;
+translate(State, [], Path) ->
+ case rebar_file_utils:path_from_ancestor(Path, rebar_state:dir(State)) of
+ {ok, P} -> filename:join([rebar_dir:base_dir(State), "extras", P]);
+ %% not relative, leave as is
+ {error, badparent} -> Path
+ end.
+
+setup_logdir(State, Opts) ->
+ Logdir = case proplists:get_value(logdir, Opts) of
+ undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
+ Dir -> Dir
+ end,
+ filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])),
+ [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
+
+turn_off_auto_compile(Opts) ->
+ [{auto_compile, false}|lists:keydelete(auto_compile, 1, Opts)].
+
run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)).
run_test_quiet(Opts) ->
@@ -171,272 +557,47 @@ format_skipped({0, 0}) ->
format_skipped({User, Auto}) ->
io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
-test_state(State) ->
- TestOpts = case rebar_state:get(State, ct_compile_opts, []) of
- [] -> [];
- Opts -> [{erl_opts, Opts}]
- end,
- [first_files(State)|TestOpts].
-
-first_files(State) ->
- CTFirst = rebar_state:get(State, ct_first_files, []),
- {erl_first_files, CTFirst}.
-
-setup_ct(State) ->
- Opts = resolve_ct_opts(State),
- Opts1 = discover_tests(State, Opts),
- copy_and_compile_tests(State, Opts1).
-
-resolve_ct_opts(State) ->
- {RawOpts, _} = rebar_state:command_parsed_args(State),
- CmdOpts = transform_opts(RawOpts),
- CfgOpts = rebar_state:get(State, ct_opts, []),
- Merged = lists:ukeymerge(1,
- lists:ukeysort(1, CmdOpts),
- lists:ukeysort(1, CfgOpts)),
- %% make sure `dir` and/or `suite` from command line go in as
- %% a pair overriding both `dir` and `suite` from config if
- %% they exist
- case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of
- {undefined, undefined} -> Merged;
- {_Suite, undefined} -> lists:keydelete(dir, 1, Merged);
- {undefined, _Dir} -> lists:keydelete(suite, 1, Merged);
- {_Suite, _Dir} -> Merged
- end.
-
-discover_tests(State, Opts) ->
- case proplists:get_value(spec, Opts) of
- undefined -> discover_dirs_and_suites(State, Opts);
- TestSpec -> discover_testspec(TestSpec, Opts)
- end.
-
-discover_dirs_and_suites(State, Opts) ->
- case {proplists:get_value(dir, Opts), proplists:get_value(suite, Opts)} of
- %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test`
- %% as suites
- {undefined, undefined} -> test_dirs(State, Opts);
- %% no dirs defined
- {undefined, _} -> Opts;
- %% no suites defined
- {_, undefined} -> Opts;
- %% a single dir defined, this is ok
- {Dirs, Suites} when is_integer(hd(Dirs)), is_list(Suites) -> Opts;
- %% still a single dir defined, adjust to make acceptable to ct
- {[Dir], Suites} when is_integer(hd(Dir)), is_list(Suites) ->
- [{dir, Dir}|lists:keydelete(dir, 1, Opts)];
- %% multiple dirs and suites, error now to simplify later steps
- {_, _} -> throw({error, {multiple_dirs_and_suites, Opts}})
- end.
-
-discover_testspec(_TestSpec, Opts) ->
- lists:keydelete(auto_compile, 1, Opts).
-
-copy_and_compile_tests(State, Opts) ->
- %% possibly enable cover
+maybe_cover_compile(State) ->
{RawOpts, _} = rebar_state:command_parsed_args(State),
State1 = case proplists:get_value(cover, RawOpts, false) of
true -> rebar_state:set(State, cover_enabled, true);
false -> State
end,
- copy_and_compile_test_suites(State1, Opts).
-
-copy_and_compile_test_suites(State, Opts) ->
- case proplists:get_value(suite, Opts) of
- %% no suites, try dirs
- undefined -> copy_and_compile_test_dirs(State, Opts);
- Suites ->
- Dir = proplists:get_value(dir, Opts, undefined),
- AllSuites = join(Dir, Suites),
- Dirs = find_suite_dirs(AllSuites),
- lists:foreach(fun(S) ->
- NewPath = copy(State, S),
- compile_dir(State, NewPath)
- end, Dirs),
- NewSuites = lists:map(fun(S) -> retarget_path(State, S) end, AllSuites),
- [{suite, NewSuites}|lists:keydelete(suite, 1, Opts)]
- end.
-
-copy_and_compile_test_dirs(State, Opts) ->
- case proplists:get_value(dir, Opts) of
- undefined -> {error, {no_tests_specified, Opts}};
- %% dir is a single directory
- Dir when is_list(Dir), is_integer(hd(Dir)) ->
- NewPath = copy(State, Dir),
- [{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, NewPath)
- end, Dirs),
- [{dir, NewDirs}|lists:keydelete(dir, 1, Opts)]
- end.
-
-join(undefined, Suites) -> Suites;
-join(Dir, Suites) when is_list(Dir), is_integer(hd(Dir)) ->
- lists:map(fun(S) -> filename:join([Dir, S]) end, Suites);
-%% multiple dirs or a bad dir argument, try to continue anyways
-join(_, Suites) -> Suites.
-
-find_suite_dirs(Suites) ->
- AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites),
- %% eliminate duplicates
- lists:usort(AllDirs).
-
-copy(State, Dir) ->
- From = reduce_path(Dir),
- retarget_path(State, From).
-
-compile_dir(State, Dir) ->
- NewState = replace_src_dirs(State, [filename:absname(Dir)]),
- ok = rebar_erlc_compiler:compile(rebar_state:opts(NewState), rebar_dir:base_dir(State), Dir),
- ok = maybe_cover_compile(State, Dir),
- Dir.
-
-retarget_path(State, Path) ->
- ProjectApps = rebar_state:project_apps(State),
- retarget_path(State, Path, ProjectApps).
-
-%% not relative to any apps in project, check to see it's relative to
-%% project root
-retarget_path(State, Path, []) ->
- case relative_path(reduce_path(Path), rebar_state:dir(State)) of
- {ok, NewPath} -> filename:join([rebar_dir:base_dir(State), NewPath]);
- %% not relative to project root, don't modify
- {error, not_relative} -> Path
- end;
-%% relative to current app, retarget to the same dir relative to
-%% the app's out_dir
-retarget_path(State, Path, [App|Rest]) ->
- case relative_path(reduce_path(Path), rebar_app_info:dir(App)) of
- {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]);
- {error, not_relative} -> retarget_path(State, Path, Rest)
- end.
-
-relative_path(Target, To) ->
- relative_path1(filename:split(filename:absname(Target)),
- filename:split(filename:absname(To))).
-
-relative_path1([Part|Target], [Part|To]) -> relative_path1(Target, To);
-relative_path1([], []) -> {ok, ""};
-relative_path1(Target, []) -> {ok, filename:join(Target)};
-relative_path1(_, _) -> {error, not_relative}.
-
-reduce_path(Dir) -> reduce_path([], filename:split(filename:absname(Dir))).
-
-reduce_path([], []) -> filename:nativename("/");
-reduce_path(Acc, []) -> filename:join(lists:reverse(Acc));
-reduce_path(Acc, ["."|Rest]) -> reduce_path(Acc, Rest);
-reduce_path([_|Acc], [".."|Rest]) -> reduce_path(Acc, Rest);
-reduce_path([], [".."|Rest]) -> reduce_path([], Rest);
-reduce_path(Acc, [Component|Rest]) -> reduce_path([Component|Acc], Rest).
-
-replace_src_dirs(State, Dirs) ->
- %% replace any `src_dirs` with the test dirs
- ErlOpts = rebar_state:get(State, erl_opts, []),
- 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).
+ rebar_prv_cover:maybe_cover_compile(State1).
-test_dirs(State, Opts) ->
- BareTest = filename:join([rebar_state:dir(State), "test"]),
- F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
- TestApps = project_apps(State),
- case filelib:is_dir(BareTest) andalso not lists:any(F, TestApps) of
- %% `test` dir at root of project is already scheduled to be
- %% included or `test` does not exist
- false -> application_dirs(TestApps, Opts, []);
- %% need to add `test` dir at root to dirs to be included
- true -> application_dirs(TestApps, Opts, [BareTest])
- end.
-
-project_apps(State) ->
- filter_checkouts(rebar_state:project_apps(State)).
-
-filter_checkouts(Apps) -> filter_checkouts(Apps, []).
-
-filter_checkouts([], Acc) -> lists:reverse(Acc);
-filter_checkouts([App|Rest], Acc) ->
- case rebar_app_info:is_checkout(App) of
- true -> filter_checkouts(Rest, Acc);
- false -> filter_checkouts(Rest, [App|Acc])
- end.
-
-application_dirs([], Opts, []) -> Opts;
-application_dirs([], Opts, [Acc]) -> [{dir, Acc}|Opts];
-application_dirs([], Opts, Acc) -> [{dir, lists:reverse(Acc)}|Opts];
-application_dirs([App|Rest], Opts, Acc) ->
- TestDir = filename:join([rebar_app_info:dir(App), "test"]),
- case filelib:is_dir(TestDir) of
- true -> application_dirs(Rest, Opts, [TestDir|Acc]);
- false -> application_dirs(Rest, Opts, Acc)
- end.
-
-setup_logdir(State, Opts) ->
- Logdir = case proplists:get_value(logdir, Opts) of
- undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
- Dir -> Dir
- end,
- ensure_dir([Logdir]),
- [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
-
-ensure_dir([]) -> ok;
-ensure_dir([Dir|Rest]) ->
- case ec_file:is_dir(Dir) of
- true ->
- ok;
- false ->
- ec_file:mkdir_path(Dir)
- end,
- ensure_dir(Rest).
-
-maybe_cover_compile(State, Dir) ->
- {Opts, _} = rebar_state:command_parsed_args(State),
- State1 = case proplists:get_value(cover, Opts, false) of
+maybe_write_coverdata(State) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ State1 = case proplists:get_value(cover, RawOpts, false) of
true -> rebar_state:set(State, cover_enabled, true);
false -> State
end,
- rebar_prv_cover:maybe_cover_compile(State1, [Dir]).
+ rebar_prv_cover:maybe_write_coverdata(State1, ?PROVIDER).
ct_opts(_State) ->
[{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
{suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
{group, undefined, "group", string, help(group)}, %% comma-seperated list
{testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list
- {spec, undefined, "spec", string, help(spec)}, %% comma-seperated list
- {join_specs, undefined, "join_specs", boolean, help(join_specs)}, %% Boolean
{label, undefined, "label", string, help(label)}, %% String
{config, undefined, "config", string, help(config)}, %% comma-seperated list
- {userconfig, undefined, "userconfig", string, help(userconfig)}, %% [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings}
{allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
{logdir, undefined, "logdir", string, help(logdir)}, %% dir
- {logopts, undefined, "logopts", string, help(logopts)}, %% enum, no_nl | no_src
- {verbosity, undefined, "verbosity", string, help(verbosity)}, %% Integer OR [{Category, VLevel}]
- {silent_connections, undefined, "silent_connections", string,
- help(silent_connections)}, % all OR %% comma-seperated list
- {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% file
+ {logopts, undefined, "logopts", string, help(logopts)}, %% comma seperated list
+ {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer
{cover, $c, "cover", {boolean, false}, help(cover)},
- {cover_spec, undefined, "cover_spec", string, help(cover_spec)}, %% file
- {cover_stop, undefined, "cover_stop", boolean, help(cover_stop)}, %% Boolean
- {event_handler, undefined, "event_handler", string, help(event_handler)}, %% EH | [EH] WHERE EH atom() | {atom(), InitArgs} | {[atom()], InitArgs}
- {include, undefined, "include", string, help(include)}, % comma-seperated list
- {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true},
- help(abort_if_missing_suites)}, %% boolean
- {multiply_timetraps, undefined, "multiply_timetraps", integer,
- help(multiply_timetraps)}, %% integer
- {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)}, %% Boolean
- {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, %% enum: auto_per_run | auto_per_tc | manual_per_tc
{repeat, undefined, "repeat", integer, help(repeat)}, %% integer
{duration, undefined, "duration", string, help(duration)}, % format: HHMMSS
{until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS]
- {force_stop, undefined, "force_stop", string, help(force_stop)}, % enum: skip_rest, bool
- {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Booloean
- {ct_hooks, undefined, "ct_hooks", string, help(ct_hooks)}, %% List: [CTHModule | {CTHModule, CTHInitArgs}] where CTHModule is atom CthInitArgs is term
- {auto_compile, undefined, "auto_compile", {boolean, false}, help(auto_compile)},
+ {force_stop, undefined, "force_stop", string, help(force_stop)}, %% String
+ {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Boolean
+ {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% String
+ {decrypt_key, undefined, "decrypt_key", string, help(decrypt_key)}, %% String
+ {decrypt_file, undefined, "decrypt_file", string, help(decrypt_file)}, %% String
+ {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, help(abort_if_missing_suites)}, %% Boolean
+ {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer
+ {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)},
+ {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)},
+ {readable, undefined, "readable", boolean, help(readable)},
{verbose, $v, "verbose", boolean, help(verbose)}
].
@@ -448,28 +609,20 @@ help(group) ->
"List of test groups to run";
help(testcase) ->
"List of test cases to run";
-help(spec) ->
- "List of test specs to run";
help(label) ->
"Test label";
help(config) ->
"List of config files";
+help(allow_user_terms) ->
+ "Allow user defined config values in config files";
help(logdir) ->
"Log folder";
+help(logopts) ->
+ "Options for common test logging";
help(verbosity) ->
"Verbosity";
-help(stylesheet) ->
- "Stylesheet to use for test results";
help(cover) ->
"Generate cover data";
-help(cover_spec) ->
- "Cover file to use";
-help(event_handler) ->
- "Event handlers to attach to the runner";
-help(include) ->
- "Include folder";
-help(abort_if_missing_suites) ->
- "Abort if suites are missing";
help(repeat) ->
"How often to repeat tests";
help(duration) ->
@@ -477,85 +630,27 @@ help(duration) ->
help(until) ->
"Run until (format: HHMMSS)";
help(force_stop) ->
- "Force stop after time";
+ "Force stop on test timeout (true | false | skip_rest)";
help(basic_html) ->
"Show basic HTML";
+help(stylesheet) ->
+ "CSS stylesheet to apply to html output";
+help(decrypt_key) ->
+ "Path to key for decrypting config";
+help(decrypt_file) ->
+ "Path to file containing key for decrypting config";
+help(abort_if_missing_suites) ->
+ "Abort if suites are missing";
+help(multiply_timetraps) ->
+ "Multiply timetraps";
+help(scale_timetraps) ->
+ "Scale timetraps";
+help(create_priv_dir) ->
+ "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)";
+help(readable) ->
+ "Shows test case names and only displays logs to shell on failures";
help(verbose) ->
"Verbose output";
help(_) ->
"".
-transform_opts(Opts) ->
- transform_opts(Opts, []).
-
-transform_opts([], Acc) -> Acc;
-%% drop `cover` and `verbose` so they're not passed as an option to common_test
-transform_opts([{cover, _}|Rest], Acc) ->
- transform_opts(Rest, Acc);
-transform_opts([{cover_spec, CoverSpec}|Rest], Acc) ->
- transform_opts(Rest, [{cover, CoverSpec}|Acc]);
-transform_opts([{verbose, _}|Rest], Acc) ->
- transform_opts(Rest, Acc);
-transform_opts([{ct_hooks, CtHooks}|Rest], Acc) ->
- transform_opts(Rest, [{ct_hooks, parse_term(CtHooks)}|Acc]);
-transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
- transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
-transform_opts([{force_stop, _}|Rest], Acc) ->
- transform_opts(Rest, [{force_stop, true}|Acc]);
-transform_opts([{repeat, Repeat}|Rest], Acc) ->
- transform_opts(Rest, [{repeat,
- ec_cnv:to_integer(Repeat)}|Acc]);
-transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
- transform_opts(Rest, [{create_priv_dir,
- to_atoms(CreatePrivDir)}|Acc]);
-transform_opts([{multiply_timetraps, MultiplyTimetraps}|Rest], Acc) ->
- transform_opts(Rest, [{multiply_timetraps,
- ec_cnv:to_integer(MultiplyTimetraps)}|Acc]);
-transform_opts([{event_handler, EventHandler}|Rest], Acc) ->
- transform_opts(Rest, [{event_handler, parse_term(EventHandler)}|Acc]);
-transform_opts([{silent_connections, "all"}|Rest], Acc) ->
- transform_opts(Rest, [{silent_connections, all}|Acc]);
-transform_opts([{silent_connections, SilentConnections}|Rest], Acc) ->
- transform_opts(Rest, [{silent_connections,
- to_atoms(split_string(SilentConnections))}|Acc]);
-transform_opts([{verbosity, Verbosity}|Rest], Acc) ->
- transform_opts(Rest, [{verbosity, parse_term(Verbosity)}|Acc]);
-transform_opts([{logopts, LogOpts}|Rest], Acc) ->
- transform_opts(Rest, [{logopts, to_atoms(split_string(LogOpts))}|Acc]);
-transform_opts([{userconfig, UserConfig}|Rest], Acc) ->
- transform_opts(Rest, [{userconfig, parse_term(UserConfig)}|Acc]);
-transform_opts([{testcase, Testcase}|Rest], Acc) ->
- transform_opts(Rest, [{testcase, to_atoms(split_string(Testcase))}|Acc]);
-transform_opts([{group, Group}|Rest], Acc) -> % @TODO handle ""
- % Input is a list or an atom. It can also be a nested list.
- transform_opts(Rest, [{group, parse_term(Group)}|Acc]);
-transform_opts([{suite, Suite}|Rest], Acc) ->
- transform_opts(Rest, [{suite, split_string(Suite)}|Acc]);
-transform_opts([{Key, Val}|Rest], Acc) when is_list(Val) ->
- % Default to splitting a string on comma, that works fine for both flat
- % lists of which there are many and single-items.
- Val1 = case split_string(Val) of
- [Val2] ->
- Val2;
- Val2 ->
- Val2
- end,
- transform_opts(Rest, [{Key, Val1}|Acc]);
-transform_opts([{Key, Val}|Rest], Acc) ->
- transform_opts(Rest, [{Key, Val}|Acc]).
-
-to_atoms(List) ->
- lists:map(fun(X) -> list_to_atom(X) end, List).
-
-split_string(String) ->
- string:tokens(String, ",").
-
-parse_term(String) ->
- String1 = "[" ++ String ++ "].",
- {ok, Tokens, _} = erl_scan:string(String1),
- case erl_parse:parse_term(Tokens) of
- {ok, [Terms]} ->
- Terms;
- Term ->
- Term
- end.
diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl
index 2996aee..30af90b 100644
--- a/src/rebar_prv_compile.erl
+++ b/src/rebar_prv_compile.erl
@@ -217,6 +217,10 @@ copy(OldAppDir, AppDir, Dir) ->
%% TODO: use ec_file:copy/2 to do this, it preserves timestamps and
%% may prevent recompilation of files in extra dirs
+copy(Source, Source) ->
+ %% allow users to specify a directory in _build as a directory
+ %% containing additional source/tests
+ ok;
copy(Source, Target) ->
%% important to do this so no files are copied onto themselves
%% which truncates them to zero length on some platforms
@@ -243,6 +247,21 @@ resolve_src_dirs(Opts) ->
%% in src_dirs also exist in extra_src_dirs
normalize_src_dirs(SrcDirs, ExtraDirs) ->
S = lists:usort(SrcDirs),
- E = lists:usort(ExtraDirs),
- {S, lists:subtract(E, S)}.
+ E = lists:subtract(lists:usort(ExtraDirs), S),
+ ok = warn_on_problematic_directories(S ++ E),
+ {S, E}.
+
+%% warn when directories called `eunit' and `ct' are added to compile dirs
+warn_on_problematic_directories(AllDirs) ->
+ F = fun(Dir) ->
+ case is_a_problem(Dir) of
+ true -> ?WARN("Possible name clash with directory ~p.", [Dir]);
+ false -> ok
+ end
+ end,
+ lists:foreach(F, AllDirs).
+
+is_a_problem("eunit") -> true;
+is_a_problem("common_test") -> true;
+is_a_problem(_) -> false.
diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl
index 0b9b9bb..c915141 100644
--- a/src/rebar_prv_cover.erl
+++ b/src/rebar_prv_cover.erl
@@ -207,6 +207,8 @@ format_table(Stats, CoverFiles) ->
MaxLength = max(lists:foldl(fun max_length/2, 0, Stats), 20),
Header = header(MaxLength),
Seperator = seperator(MaxLength),
+ TotalLabel = format("total", MaxLength),
+ TotalCov = format(calculate_total(Stats), 8),
[io_lib:format("~ts~n~ts~n~ts~n", [Seperator, Header, Seperator]),
lists:map(fun({Mod, Coverage}) ->
Name = format(Mod, MaxLength),
@@ -214,6 +216,8 @@ format_table(Stats, CoverFiles) ->
io_lib:format(" | ~ts | ~ts |~n", [Name, Cov])
end, Stats),
io_lib:format("~ts~n", [Seperator]),
+ io_lib:format(" | ~ts | ~ts |~n", [TotalLabel, TotalCov]),
+ io_lib:format("~ts~n", [Seperator]),
io_lib:format(" coverage calculated from:~n", []),
lists:map(fun(File) ->
io_lib:format(" ~ts~n", [File])
@@ -234,6 +238,18 @@ seperator(Width) ->
format(String, Width) -> io_lib:format("~*.ts", [Width, String]).
+calculate_total(Stats) when length(Stats) =:= 0 ->
+ "0%";
+calculate_total(Stats) ->
+ TotalStats = length(Stats),
+ TotalCovInt = round(lists:foldl(
+ fun({_Mod, Coverage, _File}, Acc) ->
+ Acc + (list_to_integer(string:strip(Coverage, right, $%)) / TotalStats);
+ ({_Mod, Coverage}, Acc) ->
+ Acc + (list_to_integer(string:strip(Coverage, right, $%)) / TotalStats)
+ end, 0, Stats)),
+ integer_to_list(TotalCovInt) ++ "%".
+
write_index(State, Coverage) ->
CoverDir = cover_dir(State),
FileName = filename:join([CoverDir, "index.html"]),
@@ -265,6 +281,8 @@ write_index_section(F, [{Section, DataFile, Mods}|Rest]) ->
[strip_coverdir(Report), Mod, Cov])
end,
lists:foreach(fun(M) -> ok = file:write(F, FmtLink(M)) end, Mods),
+ ok = file:write(F, ?FMT("<tr><td><strong>Total</strong></td><td>~ts</td>\n",
+ [calculate_total(Mods)])),
ok = file:write(F, "</table>\n"),
write_index_section(F, Rest).
@@ -279,21 +297,26 @@ cover_compile(State, apps) ->
Apps = filter_checkouts(rebar_state:project_apps(State)),
AppDirs = app_dirs(Apps),
ExtraDirs = extra_src_dirs(State, Apps),
- cover_compile(State, AppDirs ++ ExtraDirs);
+ cover_compile(State, lists:filter(fun(D) -> ec_file:is_dir(D) end, AppDirs ++ ExtraDirs));
cover_compile(State, Dirs) ->
%% start the cover server if necessary
{ok, CoverPid} = start_cover(),
%% redirect cover output
true = redirect_cover_output(State, CoverPid),
- CompileResult = compile(Dirs, []),
- %% print any warnings about modules that failed to cover compile
- lists:foreach(fun print_cover_warnings/1, lists:flatten(CompileResult)).
-
-compile([], Acc) -> lists:reverse(Acc);
-compile([Dir|Rest], Acc) ->
- ?INFO("covering ~p", [Dir]),
- Result = cover:compile_beam_directory(Dir),
- compile(Rest, [Result|Acc]).
+ lists:foreach(fun(Dir) ->
+ ?DEBUG("cover compiling ~p", [Dir]),
+ case catch(cover:compile_beam_directory(Dir)) of
+ {error, eacces} ->
+ ?WARN("Directory ~p not readable, modules will not be included in coverage", [Dir]);
+ {error, enoent} ->
+ ?WARN("Directory ~p not found", [Dir]);
+ {'EXIT', {Reason, _}} ->
+ ?WARN("Cover compilation for directory ~p failed: ~p", [Dir, Reason]);
+ Results ->
+ %% print any warnings about modules that failed to cover compile
+ lists:foreach(fun print_cover_warnings/1, lists:flatten(Results))
+ end
+ end, Dirs).
app_dirs(Apps) ->
lists:foldl(fun app_ebin_dirs/2, [], Apps).
@@ -302,7 +325,7 @@ app_ebin_dirs(App, Acc) ->
AppDir = rebar_app_info:ebin_dir(App),
ExtraDirs = rebar_dir:extra_src_dirs(rebar_app_info:opts(App), []),
OutDir = rebar_app_info:out_dir(App),
- [filename:join([OutDir, D]) || D <- [AppDir|ExtraDirs]] ++ Acc.
+ [AppDir] ++ [filename:join([OutDir, D]) || D <- ExtraDirs] ++ Acc.
extra_src_dirs(State, Apps) ->
BaseDir = rebar_state:dir(State),
@@ -339,9 +362,8 @@ redirect_cover_output(State, CoverPid) ->
group_leader(F, CoverPid).
print_cover_warnings({ok, _}) -> ok;
-print_cover_warnings({error, File}) ->
- ?WARN("Cover compilation of ~p failed, module is not included in cover data.",
- [File]).
+print_cover_warnings({error, Error}) ->
+ ?WARN("Cover compilation failed: ~p", [Error]).
write_coverdata(State, Task) ->
DataDir = cover_dir(State),
diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl
index 487e9d1..834eb98 100644
--- a/src/rebar_prv_dialyzer.erl
+++ b/src/rebar_prv_dialyzer.erl
@@ -173,7 +173,7 @@ do_update_proj_plt(State, Plt, Output) ->
case read_plt(State, Plt) of
{ok, OldFiles} ->
check_plt(State, Plt, Output, OldFiles, Files);
- {error, no_such_file} ->
+ error ->
build_proj_plt(State, Plt, Output, Files)
end.
@@ -252,14 +252,25 @@ read_plt(_State, Plt) ->
case dialyzer:plt_info(Plt) of
{ok, Info} ->
Files = proplists:get_value(files, Info, []),
- {ok, Files};
- {error, no_such_file} = Error ->
- Error;
+ read_plt_files(Plt, Files);
+ {error, no_such_file} ->
+ error;
{error, read_error} ->
Error = io_lib:format("Could not read the PLT file ~p", [Plt]),
throw({dialyzer_error, Error})
end.
+%% If any file no longer exists dialyzer will fail when updating the PLT.
+read_plt_files(Plt, Files) ->
+ case [File || File <- Files, not filelib:is_file(File)] of
+ [] ->
+ {ok, Files};
+ Missing ->
+ ?INFO("Could not find ~p files in ~p...", [length(Missing), Plt]),
+ ?DEBUG("Could not find files: ~p", [Missing]),
+ error
+ end.
+
check_plt(State, Plt, Output, OldList, FilesList) ->
Old = sets:from_list(OldList),
Files = sets:from_list(FilesList),
@@ -337,7 +348,7 @@ update_base_plt(State, BasePlt, Output, BaseFiles) ->
case read_plt(State, BasePlt) of
{ok, OldBaseFiles} ->
check_plt(State, BasePlt, Output, OldBaseFiles, BaseFiles);
- {error, no_such_file} ->
+ error ->
_ = filelib:ensure_dir(BasePlt),
build_plt(State, BasePlt, Output, BaseFiles)
end.
@@ -386,7 +397,7 @@ run_dialyzer(State, Opts, Output) ->
case proplists:get_bool(get_warnings, Opts) of
true ->
WarningsList = get_config(State, warnings, []),
- Opts2 = [{warnings, WarningsList},
+ Opts2 = [{warnings, legacy_warnings(WarningsList)},
{check_plt, false} |
Opts],
?DEBUG("Running dialyzer with options: ~p~n", [Opts2]),
@@ -401,6 +412,14 @@ run_dialyzer(State, Opts, Output) ->
{0, State}
end.
+legacy_warnings(Warnings) ->
+ case dialyzer_version() of
+ TupleVsn when TupleVsn < {2, 8, 0} ->
+ [Warning || Warning <- Warnings, Warning =/= unknown];
+ _ ->
+ Warnings
+ end.
+
format_warnings(Output, Warnings) ->
Warnings1 = rebar_dialyzer_format:format_warnings(Warnings),
console_warnings(Warnings1),
@@ -464,3 +483,16 @@ collect_nested_dependent_apps(App, Seen) ->
end
end
end.
+
+dialyzer_version() ->
+ _ = application:load(dialyzer),
+ {ok, Vsn} = application:get_key(dialyzer, vsn),
+ case string:tokens(Vsn, ".") of
+ [Major, Minor] ->
+ version_tuple(Major, Minor, "0");
+ [Major, Minor, Patch | _] ->
+ version_tuple(Major, Minor, Patch)
+ end.
+
+version_tuple(Major, Minor, Patch) ->
+ {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.
diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl
index 2c687ac..a1a4408 100644
--- a/src/rebar_prv_eunit.erl
+++ b/src/rebar_prv_eunit.erl
@@ -9,7 +9,7 @@
do/1,
format_error/1]).
%% exported solely for tests
--export([compile/2, prepare_tests/1, eunit_opts/1]).
+-export([prepare_tests/1, eunit_opts/1, validate_tests/2]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -39,24 +39,26 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Tests = prepare_tests(State),
- case compile(State, Tests) of
+ %% inject `eunit_first_files`, `eunit_compile_opts` and any
+ %% directories required by tests into the applications
+ NewState = inject_eunit_state(State, Tests),
+ case compile(NewState) of
%% successfully compiled apps
{ok, S} -> do(S, Tests);
- %% this should look like a compiler error, not an eunit error
Error -> Error
end.
do(State, Tests) ->
?INFO("Performing EUnit tests...", []),
- rebar_utils:update_code(rebar_state:code_paths(State, all_deps)),
+ rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]),
%% Run eunit provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
- case Tests of
+ case validate_tests(State, Tests) of
{ok, T} ->
case run_tests(State, T) of
{ok, State1} ->
@@ -95,6 +97,8 @@ format_error({error_running_tests, Reason}) ->
format_error({eunit_test_errors, Errors}) ->
io_lib:format(lists:concat(["Error Running EUnit Tests:"] ++
lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []);
+format_error({badconfig, {Msg, {Value, Key}}}) ->
+ io_lib:format(Msg, [Value, Key]);
format_error({error, Error}) ->
format_error({error_running_tests, Error}).
@@ -102,45 +106,150 @@ format_error({error, Error}) ->
%% Internal functions
%% ===================================================================
-compile(State, {ok, Tests}) ->
- %% inject `eunit_first_files`, `eunit_compile_opts` and any
- %% directories required by tests into the applications
- NewState = inject_eunit_state(State, Tests),
+prepare_tests(State) ->
+ %% parse and translate command line tests
+ CmdTests = resolve_tests(State),
+ CfgTests = cfg_tests(State),
+ ProjectApps = rebar_state:project_apps(State),
+ %% prioritize tests to run first trying any command line specified
+ %% tests falling back to tests specified in the config file finally
+ %% running a default set if no other tests are present
+ select_tests(State, ProjectApps, CmdTests, CfgTests).
- case rebar_prv_compile:do(NewState) of
- %% successfully compiled apps
- {ok, S} ->
- ok = maybe_cover_compile(S),
- {ok, S};
- %% this should look like a compiler error, not an eunit error
- Error -> Error
- end;
-%% maybe compile even in the face of errors?
-compile(_State, Error) -> Error.
+resolve_tests(State) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ Apps = resolve(app, application, RawOpts),
+ Applications = resolve(application, RawOpts),
+ Dirs = resolve(dir, RawOpts),
+ Files = resolve(file, RawOpts),
+ Modules = resolve(module, RawOpts),
+ Suites = resolve(suite, module, RawOpts),
+ Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites.
-inject_eunit_state(State, Tests) ->
+resolve(Flag, RawOpts) -> resolve(Flag, Flag, RawOpts).
+
+resolve(Flag, EUnitKey, RawOpts) ->
+ case proplists:get_value(Flag, RawOpts) of
+ undefined -> [];
+ Args -> lists:map(fun(Arg) -> normalize(EUnitKey, Arg) end, string:tokens(Args, [$,]))
+ end.
+
+normalize(Key, Value) when Key == dir; Key == file -> {Key, Value};
+normalize(Key, Value) -> {Key, list_to_atom(Value)}.
+
+cfg_tests(State) ->
+ case rebar_state:get(State, eunit_tests, []) of
+ Tests when is_list(Tests) ->
+ lists:map(fun({app, App}) -> {application, App}; (T) -> T end, Tests);
+ Wrong ->
+ %% probably a single non list term
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, eunit_tests}}})
+ end.
+
+select_tests(_State, _ProjectApps, {error, _} = Error, _) -> Error;
+select_tests(_State, _ProjectApps, _, {error, _} = Error) -> Error;
+select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)};
+select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests};
+select_tests(_State, _ProjectApps, Tests, _) -> {ok, Tests}.
+
+default_tests(State, Apps) ->
+ %% use `{application, App}` for each app in project
+ AppTests = set_apps(Apps),
+ %% additional test modules in `test` dir of each app
+ ModTests = set_modules(Apps, State),
+ AppTests ++ ModTests.
+
+set_apps(Apps) -> set_apps(Apps, []).
+
+set_apps([], Acc) -> Acc;
+set_apps([App|Rest], Acc) ->
+ AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
+ set_apps(Rest, [{application, AppName}|Acc]).
+
+set_modules(Apps, State) -> set_modules(Apps, State, {[], []}).
+
+set_modules([], State, {AppAcc, TestAcc}) ->
+ TestSrc = gather_src([filename:join([rebar_state:dir(State), "test"])]),
+ dedupe_tests({AppAcc, TestAcc ++ TestSrc});
+set_modules([App|Rest], State, {AppAcc, TestAcc}) ->
+ F = fun(Dir) -> filename:join([rebar_app_info:dir(App), Dir]) end,
+ AppDirs = lists:map(F, rebar_dir:src_dirs(rebar_app_info:opts(App), ["src"])),
+ AppSrc = gather_src(AppDirs),
+ TestDirs = [filename:join([rebar_app_info:dir(App), "test"])],
+ TestSrc = gather_src(TestDirs),
+ set_modules(Rest, State, {AppSrc ++ AppAcc, TestSrc ++ TestAcc}).
+
+gather_src(Dirs) -> gather_src(Dirs, []).
+
+gather_src([], Srcs) -> Srcs;
+gather_src([Dir|Rest], Srcs) ->
+ gather_src(Rest, Srcs ++ rebar_utils:find_files(Dir, "^[^._].*\\.erl\$", true)).
+
+dedupe_tests({AppMods, TestMods}) ->
+ %% for each modules in TestMods create a test if there is not a module
+ %% in AppMods that will trigger it
+ F = fun(Mod) ->
+ M = filename:basename(Mod, ".erl"),
+ MatchesTest = fun(Dir) -> filename:basename(Dir, ".erl") ++ "_tests" == M end,
+ case lists:any(MatchesTest, AppMods) of
+ false -> {true, {module, list_to_atom(M)}};
+ true -> false
+ end
+ end,
+ lists:usort(rebar_utils:filtermap(F, TestMods)).
+
+inject_eunit_state(State, {ok, Tests}) ->
Apps = rebar_state:project_apps(State),
- ModdedApps = lists:map(fun(App) ->
- NewOpts = inject(rebar_app_info:opts(App), State),
- rebar_app_info:opts(App, NewOpts)
- end, Apps),
- NewOpts = inject(rebar_state:opts(State), State),
- NewState = rebar_state:opts(State, NewOpts),
- test_dirs(NewState, ModdedApps, Tests).
-
-inject(Opts, State) ->
+ case inject_eunit_state(State, Apps, []) of
+ {ok, {NewState, ModdedApps}} ->
+ test_dirs(NewState, ModdedApps, Tests);
+ {error, _} = Error -> Error
+ end;
+inject_eunit_state(_State, Error) -> Error.
+
+inject_eunit_state(State, [App|Rest], Acc) ->
+ case inject(rebar_app_info:opts(App)) of
+ {error, _} = Error -> Error;
+ NewOpts ->
+ NewApp = rebar_app_info:opts(App, NewOpts),
+ inject_eunit_state(State, Rest, [NewApp|Acc])
+ end;
+inject_eunit_state(State, [], Acc) ->
+ case inject(rebar_state:opts(State)) of
+ {error, _} = Error -> Error;
+ NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
+ end.
+
+opts(Opts, Key, Default) ->
+ case rebar_opts:get(Opts, Key, Default) of
+ Vs when is_list(Vs) -> Vs;
+ Wrong ->
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
+ end.
+
+inject(Opts) -> erl_opts(Opts).
+
+erl_opts(Opts) ->
%% append `eunit_compile_opts` to app defined `erl_opts`
- ErlOpts = rebar_opts:get(Opts, erl_opts, []),
- EUnitOpts = rebar_state:get(State, eunit_compile_opts, []),
- NewErlOpts = EUnitOpts ++ ErlOpts,
+ ErlOpts = opts(Opts, erl_opts, []),
+ EUnitOpts = opts(Opts, eunit_compile_opts, []),
+ case append(EUnitOpts, ErlOpts) of
+ {error, _} = Error -> Error;
+ NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts))
+ end.
+
+first_files(Opts) ->
%% append `eunit_first_files` to app defined `erl_first_files`
- FirstFiles = rebar_opts:get(Opts, erl_first_files, []),
- EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []),
- NewFirstFiles = EUnitFirstFiles ++ FirstFiles,
- %% insert the new keys into the opts
- lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end,
- Opts,
- [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]).
+ FirstFiles = opts(Opts, erl_first_files, []),
+ EUnitFirstFiles = opts(Opts, eunit_first_files, []),
+ case append(EUnitFirstFiles, FirstFiles) of
+ {error, _} = Error -> Error;
+ NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles)
+ end.
+
+append({error, _} = Error, _) -> Error;
+append(_, {error, _} = Error) -> Error;
+append(A, B) -> A ++ B.
test_dirs(State, Apps, []) -> rebar_state:project_apps(State, Apps);
test_dirs(State, Apps, [{dir, Dir}|Rest]) ->
@@ -174,67 +283,23 @@ maybe_inject_test_dir(State, AppAcc, [], Dir) ->
inject_test_dir(Opts, Dir) ->
%% append specified test targets to app defined `extra_src_dirs`
- ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
+ ExtraSrcDirs = rebar_dir:extra_src_dirs(Opts),
rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
-prepare_tests(State) ->
- %% parse and translate command line tests
- CmdTests = resolve_tests(State),
- CfgTests = rebar_state:get(State, eunit_tests, []),
- ProjectApps = rebar_state:project_apps(State),
- %% prioritize tests to run first trying any command line specified
- %% tests falling back to tests specified in the config file finally
- %% running a default set if no other tests are present
- Tests = select_tests(State, ProjectApps, CmdTests, CfgTests),
- %% check applications for existence in project, modules for existence
- %% in applications, files and dirs for existence on disk and allow
- %% any unrecognized tests through for eunit to handle
- validate_tests(State, ProjectApps, Tests).
-
-resolve_tests(State) ->
- {RawOpts, _} = rebar_state:command_parsed_args(State),
- Apps = resolve(app, application, RawOpts),
- Applications = resolve(application, RawOpts),
- Dirs = resolve(dir, RawOpts),
- Files = resolve(file, RawOpts),
- Modules = resolve(module, RawOpts),
- Suites = resolve(suite, module, RawOpts),
- Apps ++ Applications ++ Dirs ++ Files ++ Modules ++ Suites.
-
-resolve(Flag, RawOpts) -> resolve(Flag, Flag, RawOpts).
-
-resolve(Flag, EUnitKey, RawOpts) ->
- case proplists:get_value(Flag, RawOpts) of
- undefined -> [];
- Args -> lists:map(fun(Arg) -> normalize(EUnitKey, Arg) end, string:tokens(Args, [$,]))
- end.
-
-normalize(Key, Value) when Key == dir; Key == file -> {Key, Value};
-normalize(Key, Value) -> {Key, list_to_atom(Value)}.
-
-select_tests(State, ProjectApps, [], []) -> default_tests(State, ProjectApps);
-select_tests(_State, _ProjectApps, [], Tests) -> Tests;
-select_tests(_State, _ProjectApps, Tests, _) -> Tests.
-
-default_tests(State, Apps) ->
- Tests = set_apps(Apps, []),
- BareTest = filename:join([rebar_state:dir(State), "test"]),
- F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
- case filelib:is_dir(BareTest) andalso not lists:any(F, Apps) of
- %% `test` dir at root of project is already scheduled to be
- %% included or `test` does not exist
- false -> lists:reverse(Tests);
- %% need to add `test` dir at root to dirs to be included
- true -> lists:reverse([{dir, BareTest}|Tests])
+compile({error, _} = Error) -> Error;
+compile(State) ->
+ case rebar_prv_compile:do(State) of
+ %% successfully compiled apps
+ {ok, S} ->
+ ok = maybe_cover_compile(S),
+ {ok, S};
+ %% this should look like a compiler error, not an eunit error
+ Error -> Error
end.
-set_apps([], Acc) -> Acc;
-set_apps([App|Rest], Acc) ->
- AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
- set_apps(Rest, [{application, AppName}|Acc]).
-
-validate_tests(State, ProjectApps, Tests) ->
- gather_tests(fun(Elem) -> validate(State, ProjectApps, Elem) end, Tests, []).
+validate_tests(State, {ok, Tests}) ->
+ gather_tests(fun(Elem) -> validate(State, Elem) end, Tests, []);
+validate_tests(_State, Error) -> Error.
gather_tests(_F, [], Acc) -> {ok, lists:reverse(Acc)};
gather_tests(F, [Test|Rest], Acc) ->
@@ -251,27 +316,31 @@ gather_errors(F, [Test|Rest], Acc) ->
{error, Error} -> gather_errors(F, Rest, [Error|Acc])
end.
-validate(State, ProjectApps, {application, App}) ->
- validate_app(State, ProjectApps, App);
-validate(State, _ProjectApps, {dir, Dir}) ->
+validate(State, {application, App}) ->
+ validate_app(State, App);
+validate(State, {dir, Dir}) ->
validate_dir(State, Dir);
-validate(State, _ProjectApps, {file, File}) ->
+validate(State, {file, File}) ->
validate_file(State, File);
-validate(State, _ProjectApps, {module, Module}) ->
+validate(State, {module, Module}) ->
validate_module(State, Module);
-validate(State, _ProjectApps, {suite, Module}) ->
+validate(State, {suite, Module}) ->
validate_module(State, Module);
-validate(State, _ProjectApps, Module) when is_atom(Module) ->
+validate(State, Module) when is_atom(Module) ->
validate_module(State, Module);
-validate(State, ProjectApps, Path) when is_list(Path) ->
+validate(State, Path) when is_list(Path) ->
case ec_file:is_dir(Path) of
- true -> validate(State, ProjectApps, {dir, Path});
- false -> validate(State, ProjectApps, {file, Path})
+ true -> validate(State, {dir, Path});
+ false -> validate(State, {file, Path})
end;
%% unrecognized tests should be included. if they're invalid eunit will error
%% and rebar.config may contain arbitrarily complex tests that are effectively
%% unvalidatable
-validate(_State, _ProjectApps, _Test) -> ok.
+validate(_State, _Test) -> ok.
+
+validate_app(State, AppName) ->
+ ProjectApps = rebar_state:project_apps(State),
+ validate_app(State, ProjectApps, AppName).
validate_app(_State, [], AppName) ->
{error, lists:concat(["Application `", AppName, "' not found in project."])};
@@ -294,18 +363,29 @@ validate_file(State, File) ->
end.
validate_module(_State, Module) ->
- Path = code:which(Module),
- case beam_lib:chunks(Path, [exports]) of
- {ok, _} -> ok;
- {error, beam_lib, _} -> {error, lists:concat(["Module `", Module, "' not found in project."])}
+ case code:which(Module) of
+ non_existing -> {error, lists:concat(["Module `", Module, "' not found in project."])};
+ _ -> ok
end.
resolve_eunit_opts(State) ->
{Opts, _} = rebar_state:command_parsed_args(State),
EUnitOpts = rebar_state:get(State, eunit_opts, []),
- case proplists:get_value(verbose, Opts, false) of
- true -> set_verbose(EUnitOpts);
- false -> EUnitOpts
+ EUnitOpts1 = case proplists:get_value(verbose, Opts, false) of
+ true -> set_verbose(EUnitOpts);
+ false -> EUnitOpts
+ end,
+ IsVerbose = lists:member(verbose, EUnitOpts1),
+ case proplists:get_value(eunit_formatters, EUnitOpts1, not IsVerbose) of
+ true -> custom_eunit_formatters(EUnitOpts1);
+ false -> EUnitOpts1
+ end.
+
+custom_eunit_formatters(Opts) ->
+ %% If `report` is already set then treat that like `eunit_formatters` is false
+ case lists:keymember(report, 1, Opts) of
+ true -> Opts;
+ false -> [no_tty, {report, {eunit_progress, [colored, profile]}} | Opts]
end.
set_verbose(Opts) ->
@@ -318,26 +398,37 @@ set_verbose(Opts) ->
translate_paths(State, Tests) -> translate_paths(State, Tests, []).
translate_paths(_State, [], Acc) -> lists:reverse(Acc);
-translate_paths(State, [{dir, Dir}|Rest], Acc) ->
- Apps = rebar_state:project_apps(State),
- translate_paths(State, Rest, [translate(State, Apps, Dir)|Acc]);
-translate_paths(State, [{file, File}|Rest], Acc) ->
- Dir = filename:dirname(File),
+translate_paths(State, [{K, _} = Path|Rest], Acc) when K == file; K == dir ->
Apps = rebar_state:project_apps(State),
- translate_paths(State, Rest, [translate(State, Apps, Dir)|Acc]);
+ translate_paths(State, Rest, [translate(State, Apps, Path)|Acc]);
translate_paths(State, [Test|Rest], Acc) ->
translate_paths(State, Rest, [Test|Acc]).
-translate(State, [App|Rest], Dir) ->
+translate(State, [App|Rest], {dir, Dir}) ->
case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
{ok, Path} -> {dir, filename:join([rebar_app_info:out_dir(App), Path])};
- {error, badparent} -> translate(State, Rest, Dir)
+ {error, badparent} -> translate(State, Rest, {dir, Dir})
end;
-translate(State, [], Dir) ->
+translate(State, [App|Rest], {file, FilePath}) ->
+ Dir = filename:dirname(FilePath),
+ File = filename:basename(FilePath),
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
+ {ok, Path} -> {file, filename:join([rebar_app_info:out_dir(App), Path, File])};
+ {error, badparent} -> translate(State, Rest, {file, FilePath})
+ end;
+translate(State, [], {dir, Dir}) ->
case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
{ok, Path} -> {dir, filename:join([rebar_dir:base_dir(State), "extras", Path])};
%% not relative, leave as is
{error, badparent} -> {dir, Dir}
+ end;
+translate(State, [], {file, FilePath}) ->
+ Dir = filename:dirname(FilePath),
+ File = filename:basename(FilePath),
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(State)) of
+ {ok, Path} -> {file, filename:join([rebar_dir:base_dir(State), "extras", Path, File])};
+ %% not relative, leave as is
+ {error, badparent} -> {file, FilePath}
end.
maybe_cover_compile(State) ->
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index 118d799..a484c5f 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -251,13 +251,12 @@ update_unseen_dep(AppInfo, Profile, Level, Deps, Apps, State, Upgrade, Seen, Loc
-spec handle_dep(rebar_state:t(), atom(), file:filename_all(), rebar_app_info:t(), list(), integer()) -> {rebar_app_info:t(), [rebar_app_info:t()], rebar_state:t()}.
handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) ->
- Profiles = rebar_state:current_profiles(State),
Name = rebar_app_info:name(AppInfo),
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
AppInfo0 = rebar_app_info:update_opts(AppInfo, rebar_app_info:opts(AppInfo), C),
AppInfo1 = rebar_app_info:apply_overrides(rebar_app_info:get(AppInfo, overrides, []), AppInfo0),
- AppInfo2 = rebar_app_info:apply_profiles(AppInfo1, Profiles),
+ AppInfo2 = rebar_app_info:apply_profiles(AppInfo1, [default, prod]),
Plugins = rebar_app_info:get(AppInfo2, plugins, []),
AppInfo3 = rebar_app_info:set(AppInfo2, {plugins, Profile}, Plugins),
@@ -269,7 +268,7 @@ handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) ->
State1 = rebar_plugins:install(State, AppInfo3),
%% Upgrade lock level to be the level the dep will have in this dep tree
- Deps = rebar_app_info:get(AppInfo3, {deps, default}, []),
+ Deps = rebar_app_info:get(AppInfo3, {deps, default}, []) ++ rebar_app_info:get(AppInfo3, {deps, prod}, []),
AppInfo4 = rebar_app_info:deps(AppInfo3, rebar_state:deps_names(Deps)),
%% Keep all overrides from the global config and this dep when parsing its deps
diff --git a/src/rebar_prv_local_install.erl b/src/rebar_prv_local_install.erl
index 65468a3..1b58859 100644
--- a/src/rebar_prv_local_install.erl
+++ b/src/rebar_prv_local_install.erl
@@ -15,7 +15,7 @@
-include_lib("kernel/include/file.hrl").
-define(PROVIDER, install).
--define(NAMESPACE, unstable).
+-define(NAMESPACE, local).
-define(DEPS, []).
%% ===================================================================
@@ -60,7 +60,7 @@ format_error(Reason) ->
bin_contents(OutputDir) ->
<<"#!/usr/bin/env sh
-erl -pz ", (ec_cnv:to_binary(OutputDir))/binary,"/*/ebin +sbtu +A0 -noshell -boot start_clean -s rebar3 main -extra \"$@\"
+erl -pz ", (ec_cnv:to_binary(OutputDir))/binary,"/*/ebin +sbtu +A0 -noshell -boot start_clean -s rebar3 main $REBAR3_ERL_ARGS -extra \"$@\"
">>.
extract_escript(State, ScriptPath) ->
diff --git a/src/rebar_prv_local_upgrade.erl b/src/rebar_prv_local_upgrade.erl
index bdfc232..aa9ee44 100644
--- a/src/rebar_prv_local_upgrade.erl
+++ b/src/rebar_prv_local_upgrade.erl
@@ -14,7 +14,7 @@
-include_lib("kernel/include/file.hrl").
-define(PROVIDER, upgrade).
--define(NAMESPACE, unstable).
+-define(NAMESPACE, local).
-define(DEPS, []).
%% ===================================================================
diff --git a/src/rebar_prv_new.erl b/src/rebar_prv_new.erl
index 6574aca..064315e 100644
--- a/src/rebar_prv_new.erl
+++ b/src/rebar_prv_new.erl
@@ -7,6 +7,7 @@
format_error/1]).
-include("rebar.hrl").
+-include_lib("providers/include/providers.hrl").
-define(PROVIDER, new).
-define(DEPS, []).
@@ -32,19 +33,19 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
- case rebar_state:command_args(State) of
+ case strip_flags(rebar_state:command_args(State)) of
["help"] ->
?CONSOLE("Call `rebar3 new help <template>` for a detailed description~n", []),
- show_short_templates(rebar_templater:list_templates(State)),
+ show_short_templates(list_templates(State)),
{ok, State};
["help", TemplateName] ->
- case lists:keyfind(TemplateName, 1, rebar_templater:list_templates(State)) of
+ case lists:keyfind(TemplateName, 1, list_templates(State)) of
false -> ?CONSOLE("template not found.", []);
Term -> show_template(Term)
end,
{ok, State};
[TemplateName | Opts] ->
- case lists:keyfind(TemplateName, 1, rebar_templater:list_templates(State)) of
+ case lists:keyfind(TemplateName, 1, list_templates(State)) of
false ->
?CONSOLE("template not found.", []);
_ ->
@@ -53,11 +54,13 @@ do(State) ->
end,
{ok, State};
[] ->
- show_short_templates(rebar_templater:list_templates(State)),
+ show_short_templates(list_templates(State)),
{ok, State}
end.
-spec format_error(any()) -> iolist().
+format_error({consult, File, Reason}) ->
+ io_lib:format("Error consulting file at ~s for reason ~p", [File, Reason]);
format_error(Reason) ->
io_lib:format("~p", [Reason]).
@@ -65,6 +68,15 @@ format_error(Reason) ->
%% Internal functions
%% ===================================================================
+list_templates(State) ->
+ lists:foldl(fun({error, {consult, File, Reason}}, Acc) ->
+ ?WARN("Error consulting template file ~s for reason ~p",
+ [File, Reason]),
+ Acc
+ ; (Tpl, Acc) ->
+ [Tpl|Acc]
+ end, [], lists:reverse(rebar_templater:list_templates(State))).
+
info() ->
io_lib:format(
"Create rebar3 project based on template and vars.~n"
@@ -72,6 +84,10 @@ info() ->
"Valid command line options:~n"
" <template> [var=foo,...]~n", []).
+strip_flags([]) -> [];
+strip_flags(["-"++_|Opts]) -> strip_flags(Opts);
+strip_flags([Opt | Opts]) -> [Opt | strip_flags(Opts)].
+
is_forced(State) ->
{Args, _} = rebar_state:command_parsed_args(State),
case proplists:get_value(force, Args) of
@@ -116,10 +132,13 @@ show_template({Name, Type, Location, Description, Vars}) ->
format_vars(Vars)]).
format_type(escript) -> "built-in";
+format_type(plugin) -> "plugin";
format_type(file) -> "custom".
format_type(escript, _) ->
"built-in template";
+format_type(plugin, Loc) ->
+ io_lib:format("plugin template (~s)", [Loc]);
format_type(file, Loc) ->
io_lib:format("custom template (~s)", [Loc]).
diff --git a/src/rebar_prv_plugins.erl b/src/rebar_prv_plugins.erl
index 20bc1ea..7e6b88e 100644
--- a/src/rebar_prv_plugins.erl
+++ b/src/rebar_prv_plugins.erl
@@ -34,34 +34,37 @@ 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(rebar_state:opts(State)), "plugins"),
- display_plugins("Global plugins", GlobalPluginsDir, GlobalPlugins),
+ GlobalPluginsDir = filename:join([rebar_dir:global_cache_dir(rebar_state:opts(State)), "plugins", "*"]),
+ GlobalApps = rebar_app_discover:find_apps([GlobalPluginsDir], all),
+ display_plugins("Global plugins", GlobalApps, GlobalPlugins),
Plugins = rebar_state:get(State, plugins, []),
- PluginsDir =rebar_dir:plugins_dir(State),
- display_plugins("Local plugins", PluginsDir, Plugins),
+ PluginsDir = filename:join(rebar_dir:plugins_dir(State), "*"),
+ CheckoutsDir = filename:join(rebar_dir:checkouts_dir(State), "*"),
+ Apps = rebar_app_discover:find_apps([CheckoutsDir, PluginsDir], all),
+ display_plugins("Local plugins", Apps, Plugins),
{ok, State}.
-spec format_error(any()) -> iolist().
format_error(Reason) ->
io_lib:format("~p", [Reason]).
-display_plugins(_Header, _Dir, []) ->
+display_plugins(_Header, _Apps, []) ->
ok;
-display_plugins(Header, Dir, Plugins) ->
+display_plugins(Header, Apps, Plugins) ->
?CONSOLE("--- ~s ---", [Header]),
- display_plugins(Dir, Plugins),
+ display_plugins(Apps, Plugins),
?CONSOLE("", []).
-display_plugins(Dir, Plugins) ->
+display_plugins(Apps, Plugins) ->
lists:foreach(fun(Plugin) ->
- Name = if is_atom(Plugin) -> Plugin;
- is_tuple(Plugin) -> element(1, Plugin)
+ Name = if is_atom(Plugin) -> ec_cnv:to_binary(Plugin);
+ is_tuple(Plugin) -> ec_cnv:to_binary(element(1, Plugin))
end,
- case rebar_app_info:discover(filename:join(Dir, Name)) of
+ case rebar_app_utils:find(Name, Apps) of
{ok, _App} ->
?CONSOLE("~s", [Name]);
- not_found ->
+ error ->
?DEBUG("Unable to find plugin ~s", [Name])
end
end, Plugins).
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index 4cf1e04..ea759fc 100644
--- a/src/rebar_prv_shell.erl
+++ b/src/rebar_prv_shell.erl
@@ -27,6 +27,7 @@
-module(rebar_prv_shell).
-author("Kresten Krab Thorup <krab@trifork.com>").
+-author("Fred Hebert <mononcqc@ferd.ca>").
-behaviour(provider).
@@ -56,12 +57,23 @@ init(State) ->
{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."},
+ "Path to the config file to use. Defaults to "
+ "{shell, [{config, File}]} and then the relx "
+ "sys.config file if not specified."},
{name, undefined, "name", atom,
"Gives a long name to the node."},
{sname, undefined, "sname", atom,
- "Gives a short name to the node."}]}
+ "Gives a short name to the node."},
+ {script_file, undefined, "script", string,
+ "Path to an escript file to run before "
+ "starting the project apps. Defaults to "
+ "rebar.config {shell, [{script_file, File}]} "
+ "if not specified."},
+ {apps, undefined, "apps", string,
+ "A list of apps to boot before starting the "
+ "shell. (E.g. --apps app1,app2,app3) Defaults "
+ "to rebar.config {shell, [{apps, Apps}]} or "
+ "relx apps if not specified."}]}
])
),
{ok, State1}.
@@ -86,6 +98,7 @@ shell(State) ->
setup_name(State),
setup_paths(State),
setup_shell(),
+ maybe_run_script(State),
%% 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)
@@ -99,22 +112,64 @@ 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(),
- proplists:get_value(group_leader, erlang:process_info(Pid)) == whereis(user)
- ],
- %% terminate the current user
+ OldUser = kill_old_user(),
+ %% Test for support here
+ NewUser = try erlang:open_port({spawn,'tty_sl -c -e'}, []) of
+ Port when is_port(Port) ->
+ true = port_close(Port),
+ setup_new_shell()
+ catch
+ error:_ ->
+ setup_old_shell()
+ end,
+ rewrite_leaders(OldUser, NewUser).
+
+kill_old_user() ->
+ OldUser = whereis(user),
+ %% terminate the current user's port, in a way that makes it shut down,
+ %% but without taking down the supervision tree so that the escript doesn't
+ %% fully die
+ [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],
+ user ! {'EXIT', P, normal}, % pretend the port died, then the port can die!
+ OldUser.
+
+setup_new_shell() ->
+ %% terminate the current user supervision structure
ok = supervisor:terminate_child(kernel_sup, user),
%% start a new shell (this also starts a new user under the correct group)
_ = user_drv:start(),
%% wait until user_drv and user have been registered (max 3 seconds)
ok = wait_until_user_started(3000),
+ whereis(user).
+
+setup_old_shell() ->
+ %% scan all processes for any with references to the old user and save them to
+ %% update later
+ NewUser = rebar_user:start(), % hikack IO stuff with fake user
+ NewUser = whereis(user),
+ NewUser.
+
+rewrite_leaders(OldUser, NewUser) ->
%% set any process that had a reference to the old user's group leader to the
%% 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)],
+ _ = [catch erlang:group_leader(NewUser, Pid)
+ || Pid <- erlang:processes(),
+ proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
+ is_process_alive(Pid)],
+ %% Application masters have the same problem, but they hold the old group
+ %% leader in their state and hold on to it. Re-point the processes whose
+ %% leaders are application masters. This can mess up a few things around
+ %% shutdown time, but is nicer than the current lock-up.
+ OldMasters = [Pid
+ || Pid <- erlang:processes(),
+ Pid < NewUser, % only change old masters
+ {_,Dict} <- [erlang:process_info(Pid, dictionary)],
+ {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
+ _ = [catch erlang:group_leader(NewUser, Pid)
+ || Pid <- erlang:processes(),
+ lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
+ OldMasters)],
try
%% enable error_logger's tty output
error_logger:swap_handler(tty),
@@ -128,12 +183,58 @@ setup_shell() ->
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).
+maybe_run_script(State) ->
+ case first_value([fun find_script_option/1,
+ fun find_script_rebar/1], State) of
+ no_value ->
+ ?DEBUG("No script_file specified.", []),
+ ok;
+ "none" ->
+ ?DEBUG("Shell script execution skipped (--script none).", []),
+ ok;
+ RelFile ->
+ File = filename:absname(RelFile),
+ try run_script_file(File)
+ catch
+ C:E ->
+ ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p",
+ [File, C, E, erlang:get_stacktrace()])
+ end
+ end.
+
+-spec find_script_option(rebar_state:t()) -> no_value | list().
+find_script_option(State) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ debug_get_value(script_file, Opts, no_value,
+ "Found script file from command line option.").
+
+-spec find_script_rebar(rebar_state:t()) -> no_value | list().
+find_script_rebar(State) ->
+ Config = rebar_state:get(State, shell, []),
+ %% Either a string, or undefined
+ debug_get_value(script_file, Config, no_value,
+ "Found script file from rebar config file.").
+
+run_script_file(File) ->
+ ?DEBUG("Extracting escript from ~p", [File]),
+ {ok, Script} = escript:extract(File, [compile_source]),
+ Beam = proplists:get_value(source, Script),
+ Mod = proplists:get_value(module, beam_lib:info(Beam)),
+ ?DEBUG("Compiled escript as ~p", [Mod]),
+ FakeFile = "/fake_path/" ++ atom_to_list(Mod),
+ {module, Mod} = code:load_binary(Mod, FakeFile, Beam),
+ ?DEBUG("Evaling ~p:main([]).", [Mod]),
+ Result = Mod:main([]),
+ ?DEBUG("Result: ~p", [Result]),
+ Result.
+
maybe_boot_apps(State) ->
case find_apps_to_boot(State) of
undefined ->
@@ -172,17 +273,42 @@ check_epmd(_) ->
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;
+ case first_value([fun find_apps_option/1,
+ fun find_apps_rebar/1,
+ fun find_apps_relx/1], State) of
+ no_value ->
+ undefined;
Apps ->
Apps
end.
+-spec find_apps_option(rebar_state:t()) -> no_value | [atom()].
+find_apps_option(State) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ case debug_get_value(apps, Opts, no_value,
+ "Found shell apps from command line option.") of
+ no_value -> no_value;
+ AppsStr ->
+ [ list_to_atom(AppStr)
+ || AppStr <- string:tokens(AppsStr, " ,:") ]
+ end.
+
+-spec find_apps_rebar(rebar_state:t()) -> no_value | list().
+find_apps_rebar(State) ->
+ ShellOpts = rebar_state:get(State, shell, []),
+ debug_get_value(apps, ShellOpts, no_value,
+ "Found shell opts from command line option.").
+
+-spec find_apps_relx(rebar_state:t()) -> no_value | list().
+find_apps_relx(State) ->
+ case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
+ {_, _, Apps} ->
+ ?DEBUG("Found shell apps from relx.", []),
+ Apps;
+ false ->
+ no_value
+ end.
+
load_apps(Apps) ->
[case application:load(App) of
ok ->
@@ -199,9 +325,14 @@ reread_config(State) ->
no_config ->
ok;
ConfigList ->
- _ = [application:set_env(Application, Key, Val)
+ try
+ [application:set_env(Application, Key, Val)
|| {Application, Items} <- ConfigList,
- {Key, Val} <- Items],
+ {Key, Val} <- Items]
+ catch _:_ ->
+ ?ERROR("The configuration file submitted could not be read "
+ "and will be ignored.", [])
+ end,
ok
end.
@@ -261,31 +392,51 @@ add_test_paths(State) ->
% First try the --config flag, then try the relx sys_config
-spec find_config(rebar_state:t()) -> [tuple()] | no_config.
find_config(State) ->
- case find_config_option(State) of
- no_config ->
- find_config_relx(State);
- Result ->
- Result
+ case first_value([fun find_config_option/1,
+ fun find_config_rebar/1,
+ fun find_config_relx/1], State) of
+ no_value ->
+ no_config;
+ Filename when is_list(Filename) ->
+ consult_config(State, Filename)
+ end.
+
+-spec first_value([Fun], State) -> no_value | Value when
+ Value :: any(),
+ State :: rebar_state:t(),
+ Fun :: fun ((State) -> no_value | Value).
+first_value([], _) -> no_value;
+first_value([Fun | Rest], State) ->
+ case Fun(State) of
+ no_value ->
+ first_value(Rest, State);
+ Value ->
+ Value
+ end.
+
+debug_get_value(Key, List, Default, Description) ->
+ case proplists:get_value(Key, List, Default) of
+ Default -> Default;
+ Value ->
+ ?DEBUG(Description, []),
+ Value
end.
--spec find_config_option(rebar_state:t()) -> [tuple()] | no_config.
+-spec find_config_option(rebar_state:t()) -> Filename::list() | no_value.
find_config_option(State) ->
{Opts, _} = rebar_state:command_parsed_args(State),
- case proplists:get_value(config, Opts) of
- undefined ->
- no_config;
- Filename ->
- consult_config(State, Filename)
- end.
+ debug_get_value(config, Opts, no_value,
+ "Found config from command line option.").
+
+-spec find_config_rebar(rebar_state:t()) -> [tuple()] | no_value.
+find_config_rebar(State) ->
+ debug_get_value(config, rebar_state:get(State, shell, []), no_value,
+ "Found config from rebar config file.").
--spec find_config_relx(rebar_state:t()) -> [tuple()] | no_config.
+-spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value.
find_config_relx(State) ->
- case proplists:get_value(sys_config, rebar_state:get(State, relx, [])) of
- undefined ->
- no_config;
- Filename ->
- consult_config(State, Filename)
- end.
+ debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value,
+ "Found config from relx.").
-spec consult_config(rebar_state:t(), string()) -> [tuple()].
consult_config(State, Filename) ->
diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl
index 6637ebe..0e3b9a0 100644
--- a/src/rebar_prv_update.erl
+++ b/src/rebar_prv_update.erl
@@ -36,26 +36,36 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
try
- RegistryDir = rebar_packages:registry_dir(State),
- filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
- HexFile = filename:join(RegistryDir, "registry"),
- ?INFO("Updating package registry...", []),
- TmpDir = ec_file:insecure_mkdtemp(),
- TmpFile = filename:join(TmpDir, "packages.gz"),
+ case rebar_packages:registry_dir(State) of
+ {ok, RegistryDir} ->
+ filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
+ HexFile = filename:join(RegistryDir, "registry"),
+ ?INFO("Updating package registry...", []),
+ TmpDir = ec_file:insecure_mkdtemp(),
+ TmpFile = filename:join(TmpDir, "packages.gz"),
- Url = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_HEX_REGISTRY),
- case httpc:request(get, {Url, []},
- [], [{stream, TmpFile}, {sync, true}],
- rebar) of
- {ok, saved_to_file} ->
- {ok, Data} = file:read_file(TmpFile),
- Unzipped = zlib:gunzip(Data),
- ok = file:write_file(HexFile, Unzipped),
- ?INFO("Writing registry to ~s", [HexFile]),
- hex_to_index(State),
- {ok, State};
- _ ->
- ?PRV_ERROR(package_index_download)
+ CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
+ case rebar_utils:url_append_path(CDN, ?REMOTE_REGISTRY_FILE) of
+ {ok, Url} ->
+ ?DEBUG("Fetching registry from ~p", [Url]),
+ case httpc:request(get, {Url, [{"User-Agent", rebar_utils:user_agent()}]},
+ [], [{stream, TmpFile}, {sync, true}],
+ rebar) of
+ {ok, saved_to_file} ->
+ {ok, Data} = file:read_file(TmpFile),
+ Unzipped = zlib:gunzip(Data),
+ ok = file:write_file(HexFile, Unzipped),
+ ?INFO("Writing registry to ~s", [HexFile]),
+ hex_to_index(State),
+ {ok, State};
+ _ ->
+ ?PRV_ERROR(package_index_download)
+ end;
+ _ ->
+ ?PRV_ERROR({package_parse_cdn, CDN})
+ end;
+ {uri_parse_error, CDN} ->
+ ?PRV_ERROR({package_parse_cdn, CDN})
end
catch
_E:C ->
@@ -64,6 +74,8 @@ do(State) ->
end.
-spec format_error(any()) -> iolist().
+format_error({package_parse_cdn, Uri}) ->
+ io_lib:format("Failed to parse CDN url: ~p", [Uri]);
format_error(package_index_download) ->
"Failed to download package index.";
format_error(package_index_write) ->
@@ -75,7 +87,7 @@ is_supported(<<"rebar3">>) -> true;
is_supported(_) -> false.
hex_to_index(State) ->
- RegistryDir = rebar_packages:registry_dir(State),
+ {ok, RegistryDir} = rebar_packages:registry_dir(State),
HexFile = filename:join(RegistryDir, "registry"),
try ets:file2tab(HexFile) of
{ok, Registry} ->
@@ -92,12 +104,24 @@ hex_to_index(State) ->
false ->
true
end;
- ({Pkg, [Vsns]}, _) when is_binary(Pkg) ->
- ets:insert(?PACKAGE_TABLE, {Pkg, Vsns});
(_, _) ->
true
end, true, Registry),
+ ets:foldl(fun({Pkg, [[]]}, _) when is_binary(Pkg) ->
+ true;
+ ({Pkg, [Vsns=[Vsn | _Rest]]}, _) when is_binary(Pkg) ->
+ %% Verify the package is of the right build tool by checking if the first
+ %% version exists in the table from the foldl above
+ case ets:member(?PACKAGE_TABLE, {Pkg, Vsn}) of
+ true ->
+ ets:insert(?PACKAGE_TABLE, {Pkg, Vsns});
+ false ->
+ true
+ end;
+ (_, _) ->
+ true
+ end, true, Registry),
ets:insert(?PACKAGE_TABLE, {package_index_version, ?PACKAGE_INDEX_VERSION}),
?INFO("Writing index to ~s", [PackageIndex]),
ets:tab2file(?PACKAGE_TABLE, PackageIndex),
diff --git a/src/rebar_relx.erl b/src/rebar_relx.erl
index 36a24f0..5d29258 100644
--- a/src/rebar_relx.erl
+++ b/src/rebar_relx.erl
@@ -30,7 +30,7 @@ do(Module, Command, Provider, State) ->
relx:main([{lib_dirs, LibDirs}
,{caller, api} | output_dir(OutputDir, Options)], AllOptions);
Config ->
- Config1 = update_config(Config),
+ Config1 = merge_overlays(Config),
relx:main([{lib_dirs, LibDirs}
,{config, Config1}
,{caller, api} | output_dir(OutputDir, Options)], AllOptions)
@@ -46,27 +46,16 @@ do(Module, Command, Provider, State) ->
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.
-
%% Don't override output_dir if the user passed one on the command line
output_dir(OutputDir, Options) ->
[{output_dir, OutputDir} || not(lists:member("-o", Options))
andalso not(lists:member("--output-dir", Options))].
+
+merge_overlays(Config) ->
+ {Overlays, Others} =
+ lists:partition(fun(C) when element(1, C) =:= overlay -> true;
+ (_) -> false
+ end, Config),
+ %% Have profile overlay entries come before others to match how profiles work elsewhere
+ NewOverlay = lists:reverse(lists:flatmap(fun({overlay, Overlay}) -> Overlay end, Overlays)),
+ [{overlay, NewOverlay} | Others].
diff --git a/src/rebar_state.erl b/src/rebar_state.erl
index 176a80b..0c07b2a 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -370,7 +370,24 @@ providers(State, NewProviders) ->
-spec add_provider(t(), providers:t()) -> t().
add_provider(State=#state_t{providers=Providers}, Provider) ->
- State#state_t{providers=[Provider | Providers]}.
+ Name = providers:impl(Provider),
+ Namespace = providers:namespace(Provider),
+ Module = providers:module(Provider),
+ case lists:any(fun(P) ->
+ case {providers:impl(P), providers:namespace(P)} of
+ {Name, Namespace} ->
+ ?DEBUG("Not adding provider ~p ~p from module ~p because it already exists from module ~p",
+ [Namespace, Name, providers:module(P), Module]),
+ true;
+ _ ->
+ false
+ end
+ end, Providers) of
+ true ->
+ State;
+ false ->
+ State#state_t{providers=[Provider | Providers]}
+ end.
create_logic_providers(ProviderModules, State0) ->
try
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index c7fb2af..2f33bfc 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -59,10 +59,14 @@ list_templates(State) ->
%% Expand a single template's value
list_template(Files, {Name, Type, File}, State) ->
- TemplateTerms = consult(load_file(Files, Type, File)),
- {Name, Type, File,
- get_template_description(TemplateTerms),
- get_template_vars(TemplateTerms, State)}.
+ case consult(load_file(Files, Type, File)) of
+ {error, Reason} ->
+ {error, {consult, File, Reason}};
+ TemplateTerms ->
+ {Name, Type, File,
+ get_template_description(TemplateTerms),
+ get_template_vars(TemplateTerms, State)}
+ end.
%% Load up the template description out from a list of attributes read in
%% a .template file.
@@ -155,9 +159,34 @@ drop_var_docs([{K,V}|Rest]) -> [{K,V} | drop_var_docs(Rest)].
create({Template, Type, File}, Files, UserVars, Force, State) ->
TemplateTerms = consult(load_file(Files, Type, File)),
Vars = drop_var_docs(override_vars(UserVars, get_template_vars(TemplateTerms, State))),
+ maybe_warn_about_name(Vars),
TemplateCwd = filename:dirname(File),
execute_template(TemplateTerms, Files, {Template, Type, TemplateCwd}, Vars, Force).
+maybe_warn_about_name(Vars) ->
+ Name = proplists:get_value(name, Vars, "valid"),
+ case validate_atom(Name) of
+ invalid ->
+ ?WARN("The 'name' variable is often associated with Erlang "
+ "module names and/or file names. The value submitted "
+ "(~s) isn't an unquoted Erlang atom. Templates "
+ "generated may contain errors.",
+ [Name]);
+ valid ->
+ ok
+ end.
+
+validate_atom(Str) ->
+ case io_lib:fread("~a", unicode:characters_to_list(Str)) of
+ {ok, [Atom], ""} ->
+ case io_lib:write_atom(Atom) of
+ "'" ++ _ -> invalid; % quoted
+ _ -> valid % unquoted
+ end;
+ _ ->
+ invalid
+ end.
+
%% Run template instructions one at a time.
execute_template([], _, {Template,_,_}, _, _) ->
?DEBUG("Template ~s applied", [Template]),
@@ -235,6 +264,7 @@ replace_var([H|T], Acc, Vars) ->
%% Load a list of all the files in the escript and on disk
find_templates(State) ->
DiskTemplates = find_disk_templates(State),
+ PluginTemplates = find_plugin_templates(State),
{MainTemplates, Files} =
case rebar_state:escript_path(State) of
undefined ->
@@ -245,19 +275,23 @@ find_templates(State) ->
F = cache_escript_files(State),
{find_escript_templates(F), F}
end,
- AvailTemplates = find_available_templates(DiskTemplates,
- MainTemplates),
+ AvailTemplates = find_available_templates([MainTemplates,
+ PluginTemplates,
+ DiskTemplates]),
?DEBUG("Available templates: ~p\n", [AvailTemplates]),
{AvailTemplates, Files}.
-find_available_templates(TemplateList1, TemplateList2) ->
- AvailTemplates = prioritize_templates(
- tag_names(TemplateList1),
- tag_names(TemplateList2)),
-
+find_available_templates(TemplateListList) ->
+ AvailTemplates = prioritize_templates(TemplateListList),
?DEBUG("Available templates: ~p\n", [AvailTemplates]),
AvailTemplates.
+prioritize_templates([TemplateList]) ->
+ tag_names(TemplateList);
+prioritize_templates([TemplateList | TemplateListList]) ->
+ prioritize_templates(tag_names(TemplateList),
+ prioritize_templates(TemplateListList)).
+
%% Scan the current escript for available files
cache_escript_files(State) ->
{ok, Files} = rebar_utils:escript_foldl(
@@ -295,6 +329,14 @@ find_other_templates(State) ->
rebar_utils:find_files(TemplateDir, ?TEMPLATE_RE)
end.
+%% Fetch template indexes that sit on disk in plugins
+find_plugin_templates(State) ->
+ [{plugin, File}
+ || App <- rebar_state:all_plugin_deps(State),
+ Priv <- [rebar_app_info:priv_dir(App)],
+ Priv =/= undefined,
+ File <- rebar_utils:find_files(Priv, ?TEMPLATE_RE)].
+
%% Take an existing list of templates and tag them by name the way
%% the user would enter it from the CLI
tag_names(List) ->
@@ -312,6 +354,10 @@ prioritize_templates([{Name, Type, File} | Rest], Valid) ->
?DEBUG("Skipping template ~p, due to presence of a built-in "
"template with the same name", [Name]),
prioritize_templates(Rest, Valid);
+ {_, plugin, _} ->
+ ?DEBUG("Skipping template ~p, due to presence of a plugin "
+ "template with the same name", [Name]),
+ prioritize_templates(Rest, Valid);
{_, file, _} ->
?DEBUG("Skipping template ~p, due to presence of a custom "
"template at ~s", [Name, File]),
@@ -323,6 +369,9 @@ prioritize_templates([{Name, Type, File} | Rest], Valid) ->
load_file(Files, escript, Name) ->
{Name, Bin} = lists:keyfind(Name, 1, Files),
Bin;
+load_file(_Files, plugin, Name) ->
+ {ok, Bin} = file:read_file(Name),
+ Bin;
load_file(_Files, file, Name) ->
{ok, Bin} = file:read_file(Name),
Bin.
@@ -338,8 +387,10 @@ consult(Cont, Str, Acc) ->
{done, Result, Remaining} ->
case Result of
{ok, Tokens, _} ->
- {ok, Term} = erl_parse:parse_term(Tokens),
- consult([], Remaining, [Term | Acc]);
+ case erl_parse:parse_term(Tokens) of
+ {ok, Term} -> consult([], Remaining, [Term | Acc]);
+ {error, Reason} -> {error, Reason}
+ end;
{eof, _Other} ->
lists:reverse(Acc);
{error, Info, _} ->
diff --git a/src/rebar_user.erl b/src/rebar_user.erl
new file mode 100644
index 0000000..f20142d
--- /dev/null
+++ b/src/rebar_user.erl
@@ -0,0 +1,757 @@
+%%% This file is a literal copy of Erlang/OTP's user.erl module, renamed
+%%% to rebar_user.erl and modified in a few place to force a shell to always
+%%% boot or to remove dead comments.
+%%%
+%%% Its usage is required because unlike the standard (new) shell, it is
+%%% not possible to get rid of the old one without killing the rebar3 escript
+%%% at the same time. As such, this module is being used to duplicate
+%%% the old shell while stealing the usage of the IO driver {fd,0,1}
+%%% (stdio) and then booting our own shell with paths and stuff in it.
+
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(rebar_user).
+-compile(inline).
+
+%% Basic standard i/o server for user interface port.
+
+-export([start/0, start/1, start_out/0]).
+-export([interfaces/1]).
+
+-define(NAME, user).
+
+%% Defines for control ops
+-define(CTRL_OP_GET_WINSIZE,100).
+
+%%
+%% The basic server and start-up.
+%%
+
+start() ->
+ start_port([eof,binary]).
+
+start([Mod,Fun|Args]) ->
+ %% Mod,Fun,Args should return a pid. That process is supposed to act
+ %% as the io port.
+ Pid = apply(Mod, Fun, Args), % This better work!
+ Id = spawn(fun() -> server(Pid) end),
+ register(?NAME, Id),
+ Id.
+
+start_out() ->
+ %% Output-only version of start/0
+ start_port([out,binary]).
+
+start_port(PortSettings) ->
+ Id = spawn(fun() -> server({fd,0,1}, PortSettings) end),
+ register(?NAME, Id),
+ Id.
+
+%% Return the pid of the shell process.
+%% Note: We can't ask the user process for this info since it
+%% may be busy waiting for data from the port.
+interfaces(User) ->
+ case process_info(User, dictionary) of
+ {dictionary,Dict} ->
+ case lists:keysearch(shell, 1, Dict) of
+ {value,Sh={shell,Shell}} when is_pid(Shell) ->
+ [Sh];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end.
+
+server(Pid) when is_pid(Pid) ->
+ process_flag(trap_exit, true),
+ link(Pid),
+ run(Pid).
+
+server(PortName,PortSettings) ->
+ process_flag(trap_exit, true),
+ Port = open_port(PortName,PortSettings),
+ run(Port).
+
+run(P) ->
+ put(read_mode,list),
+ put(encoding,latin1),
+ group_leader(self(), self()),
+ catch_loop(P, start_init_shell()).
+
+catch_loop(Port, Shell) ->
+ catch_loop(Port, Shell, queue:new()).
+
+catch_loop(Port, Shell, Q) ->
+ case catch server_loop(Port, Q) of
+ new_shell ->
+ exit(Shell, kill),
+ catch_loop(Port, start_new_shell());
+ {unknown_exit,{Shell,Reason},_} -> % shell has exited
+ case Reason of
+ normal ->
+ put_port(<<"*** ">>, Port);
+ _ ->
+ put_port(<<"*** ERROR: ">>, Port)
+ end,
+ put_port(<<"Shell process terminated! ***\n">>, Port),
+ catch_loop(Port, start_new_shell());
+ {unknown_exit,_,Q1} ->
+ catch_loop(Port, Shell, Q1);
+ {'EXIT',R} ->
+ exit(R)
+ end.
+
+link_and_save_shell(Shell) ->
+ link(Shell),
+ put(shell, Shell),
+ Shell.
+
+start_init_shell() ->
+ link_and_save_shell(shell:start(init)).
+
+start_new_shell() ->
+ link_and_save_shell(shell:start()).
+
+server_loop(Port, Q) ->
+ receive
+ {io_request,From,ReplyAs,Request} when is_pid(From) ->
+ server_loop(Port, do_io_request(Request, From, ReplyAs, Port, Q));
+ {Port,{data,Bytes}} ->
+ case get(shell) of
+ noshell ->
+ server_loop(Port, queue:snoc(Q, Bytes));
+ _ ->
+ case contains_ctrl_g_or_ctrl_c(Bytes) of
+ false ->
+ server_loop(Port, queue:snoc(Q, Bytes));
+ _ ->
+ throw(new_shell)
+ end
+ end;
+ {Port, eof} ->
+ put(eof, true),
+ server_loop(Port, Q);
+
+ %% Ignore messages from port here.
+ {'EXIT',Port,badsig} -> % Ignore badsig errors
+ server_loop(Port, Q);
+ {'EXIT',Port,What} -> % Port has exited
+ exit(What);
+
+ %% Check if shell has exited
+ {'EXIT',SomePid,What} ->
+ case get(shell) of
+ noshell ->
+ server_loop(Port, Q); % Ignore
+ _ ->
+ throw({unknown_exit,{SomePid,What},Q})
+ end;
+
+ _Other -> % Ignore other messages
+ server_loop(Port, Q)
+ end.
+
+
+get_fd_geometry(Port) ->
+ case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of
+ List when length(List) =:= 8 ->
+ <<W:32/native,H:32/native>> = list_to_binary(List),
+ {W,H};
+ _ ->
+ error
+ end.
+
+
+%% NewSaveBuffer = io_request(Request, FromPid, ReplyAs, Port, SaveBuffer)
+
+do_io_request(Req, From, ReplyAs, Port, Q0) ->
+ case io_request(Req, Port, Q0) of
+ {_Status,Reply,Q1} ->
+ _ = io_reply(From, ReplyAs, Reply),
+ Q1;
+ {exit,What} ->
+ ok = send_port(Port, close),
+ exit(What)
+ end.
+
+%% New in R13B
+%% Encoding option (unicode/latin1)
+io_request({put_chars,unicode,Chars}, Port, Q) -> % Binary new in R9C
+ case wrap_characters_to_binary(Chars, unicode, get(encoding)) of
+ error ->
+ {error,{error,put_chars},Q};
+ Bin ->
+ put_chars(Bin, Port, Q)
+ end;
+io_request({put_chars,unicode,Mod,Func,Args}, Port, Q) ->
+ case catch apply(Mod,Func,Args) of
+ Data when is_list(Data); is_binary(Data) ->
+ case wrap_characters_to_binary(Data, unicode, get(encoding)) of
+ Bin when is_binary(Bin) ->
+ put_chars(Bin, Port, Q);
+ error ->
+ {error,{error,put_chars},Q}
+ end;
+ Undef ->
+ put_chars(Undef, Port, Q)
+ end;
+io_request({put_chars,latin1,Chars}, Port, Q) -> % Binary new in R9C
+ case catch unicode:characters_to_binary(Chars, latin1, get(encoding)) of
+ Data when is_binary(Data) ->
+ put_chars(Data, Port, Q);
+ _ ->
+ {error,{error,put_chars},Q}
+ end;
+io_request({put_chars,latin1,Mod,Func,Args}, Port, Q) ->
+ case catch apply(Mod,Func,Args) of
+ Data when is_list(Data); is_binary(Data) ->
+ case
+ catch unicode:characters_to_binary(Data,latin1,get(encoding))
+ of
+ Bin when is_binary(Bin) ->
+ put_chars(Bin, Port, Q);
+ _ ->
+ {error,{error,put_chars},Q}
+ end;
+ Undef ->
+ put_chars(Undef, Port, Q)
+ end;
+io_request({get_chars,Enc,Prompt,N}, Port, Q) -> % New in R9C
+ get_chars(Prompt, io_lib, collect_chars, N, Port, Q, Enc);
+io_request({get_line,Enc,Prompt}, Port, Q) ->
+ case get(read_mode) of
+ binary ->
+ get_line_bin(Prompt,Port,Q,Enc);
+ _ ->
+ get_chars(Prompt, io_lib, collect_line, [], Port, Q, Enc)
+ end;
+io_request({get_until,Enc,Prompt,M,F,As}, Port, Q) ->
+ get_chars(Prompt, io_lib, get_until, {M,F,As}, Port, Q, Enc);
+%% End New in R13B
+io_request(getopts, Port, Q) ->
+ getopts(Port, Q);
+io_request({setopts,Opts}, Port, Q) when is_list(Opts) ->
+ setopts(Opts, Port, Q);
+io_request({requests,Reqs}, Port, Q) ->
+ io_requests(Reqs, {ok,ok,Q}, Port);
+
+%% New in R12
+io_request({get_geometry,columns},Port,Q) ->
+ case get_fd_geometry(Port) of
+ {W,_H} ->
+ {ok,W,Q};
+ _ ->
+ {error,{error,enotsup},Q}
+ end;
+io_request({get_geometry,rows},Port,Q) ->
+ case get_fd_geometry(Port) of
+ {_W,H} ->
+ {ok,H,Q};
+ _ ->
+ {error,{error,enotsup},Q}
+ end;
+%% BC with pre-R13 nodes
+io_request({put_chars,Chars}, Port, Q) ->
+ io_request({put_chars,latin1,Chars}, Port, Q);
+io_request({put_chars,Mod,Func,Args}, Port, Q) ->
+ io_request({put_chars,latin1,Mod,Func,Args}, Port, Q);
+io_request({get_chars,Prompt,N}, Port, Q) ->
+ io_request({get_chars,latin1,Prompt,N}, Port, Q);
+io_request({get_line,Prompt}, Port, Q) ->
+ io_request({get_line,latin1,Prompt}, Port, Q);
+io_request({get_until,Prompt,M,F,As}, Port, Q) ->
+ io_request({get_until,latin1,Prompt,M,F,As}, Port, Q);
+
+io_request(R, _Port, Q) -> %Unknown request
+ {error,{error,{request,R}},Q}. %Ignore but give error (?)
+
+%% Status = io_requests(RequestList, PrevStat, Port)
+%% Process a list of output requests as long as the previous status is 'ok'.
+
+io_requests([R|Rs], {ok,_Res,Q}, Port) ->
+ io_requests(Rs, io_request(R, Port, Q), Port);
+io_requests([_|_], Error, _) ->
+ Error;
+io_requests([], Stat, _) ->
+ Stat.
+
+%% put_port(DeepList, Port)
+%% Take a deep list of characters, flatten and output them to the
+%% port.
+
+put_port(List, Port) ->
+ send_port(Port, {command, List}).
+
+%% send_port(Port, Command)
+
+send_port(Port, Command) ->
+ Port ! {self(),Command},
+ ok.
+
+%% io_reply(From, ReplyAs, Reply)
+%% The function for sending i/o command acknowledgement.
+%% The ACK contains the return value.
+
+io_reply(From, ReplyAs, Reply) ->
+ From ! {io_reply,ReplyAs,Reply}.
+
+%% put_chars
+put_chars(Chars, Port, Q) when is_binary(Chars) ->
+ ok = put_port(Chars, Port),
+ {ok,ok,Q};
+put_chars(Chars, Port, Q) ->
+ case catch list_to_binary(Chars) of
+ Binary when is_binary(Binary) ->
+ put_chars(Binary, Port, Q);
+ _ ->
+ {error,{error,put_chars},Q}
+ end.
+
+expand_encoding([]) ->
+ [];
+expand_encoding([latin1 | T]) ->
+ [{encoding,latin1} | expand_encoding(T)];
+expand_encoding([unicode | T]) ->
+ [{encoding,unicode} | expand_encoding(T)];
+expand_encoding([H|T]) ->
+ [H|expand_encoding(T)].
+
+%% setopts
+setopts(Opts0,Port,Q) ->
+ Opts = proplists:unfold(
+ proplists:substitute_negations(
+ [{list,binary}],
+ expand_encoding(Opts0))),
+ case check_valid_opts(Opts) of
+ true ->
+ do_setopts(Opts,Port,Q);
+ false ->
+ {error,{error,enotsup},Q}
+ end.
+check_valid_opts([]) ->
+ true;
+check_valid_opts([{binary,_}|T]) ->
+ check_valid_opts(T);
+check_valid_opts([{encoding,Valid}|T]) when Valid =:= latin1; Valid =:= utf8; Valid =:= unicode ->
+ check_valid_opts(T);
+check_valid_opts(_) ->
+ false.
+
+do_setopts(Opts, _Port, Q) ->
+ case proplists:get_value(encoding,Opts) of
+ Valid when Valid =:= unicode; Valid =:= utf8 ->
+ put(encoding,unicode);
+ latin1 ->
+ put(encoding,latin1);
+ undefined ->
+ ok
+ end,
+ case proplists:get_value(binary, Opts) of
+ true ->
+ put(read_mode,binary),
+ {ok,ok,Q};
+ false ->
+ put(read_mode,list),
+ {ok,ok,Q};
+ _ ->
+ {ok,ok,Q}
+ end.
+
+getopts(_Port,Q) ->
+ Bin = {binary, get(read_mode) =:= binary},
+ Uni = {encoding, get(encoding)},
+ {ok,[Bin,Uni],Q}.
+
+get_line_bin(Prompt,Port,Q, Enc) ->
+ case prompt(Port, Prompt) of
+ error ->
+ {error,{error,get_line},Q};
+ ok ->
+ case {get(eof),queue:is_empty(Q)} of
+ {true,true} ->
+ {ok,eof,Q};
+ _ ->
+ get_line(Prompt,Port, Q, [], Enc)
+ end
+ end.
+
+get_line(Prompt, Port, Q, Acc, Enc) ->
+ case queue:is_empty(Q) of
+ true ->
+ receive
+ {Port,{data,Bytes}} ->
+ get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc);
+ {Port, eof} ->
+ put(eof, true),
+ {ok, eof, []};
+ {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
+ do_io_request(Req, From, ReplyAs, Port,
+ queue:new()),
+ %% No prompt.
+ get_line(Prompt, Port, Q, Acc, Enc);
+ {io_request,From,ReplyAs,Request} when is_pid(From) ->
+ do_io_request(Request, From, ReplyAs, Port, queue:new()),
+ case prompt(Port, Prompt) of
+ error ->
+ {error,{error,get_line},Q};
+ ok ->
+ get_line(Prompt, Port, Q, Acc, Enc)
+ end;
+ {'EXIT',From,What} when node(From) =:= node() ->
+ {exit,What}
+ end;
+ false ->
+ get_line_doit(Prompt, Port, Q, Acc, Enc)
+ end.
+
+get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc) ->
+ case get(shell) of
+ noshell ->
+ get_line_doit(Prompt, Port, queue:snoc(Q, Bytes),Acc,Enc);
+ _ ->
+ case contains_ctrl_g_or_ctrl_c(Bytes) of
+ false ->
+ get_line_doit(Prompt, Port, queue:snoc(Q, Bytes), Acc, Enc);
+ _ ->
+ throw(new_shell)
+ end
+ end.
+is_cr_at(Pos,Bin) ->
+ case Bin of
+ <<_:Pos/binary,$\r,_/binary>> ->
+ true;
+ _ ->
+ false
+ end.
+srch(<<>>,_,_) ->
+ nomatch;
+srch(<<X:8,_/binary>>,X,N) ->
+ {match,[{N,1}]};
+srch(<<_:8,T/binary>>,X,N) ->
+ srch(T,X,N+1).
+
+get_line_doit(Prompt, Port, Q, Accu, Enc) ->
+ case queue:is_empty(Q) of
+ true ->
+ case get(eof) of
+ true ->
+ case Accu of
+ [] ->
+ {ok,eof,Q};
+ _ ->
+ {ok,binrev(Accu,[]),Q}
+ end;
+ _ ->
+ get_line(Prompt, Port, Q, Accu, Enc)
+ end;
+ false ->
+ Bin = queue:head(Q),
+ case srch(Bin,$\n,0) of
+ nomatch ->
+ X = byte_size(Bin)-1,
+ case is_cr_at(X,Bin) of
+ true ->
+ <<D:X/binary,_/binary>> = Bin,
+ get_line_doit(Prompt, Port, queue:tail(Q),
+ [<<$\r>>,D|Accu], Enc);
+ false ->
+ get_line_doit(Prompt, Port, queue:tail(Q),
+ [Bin|Accu], Enc)
+ end;
+ {match,[{Pos,1}]} ->
+ %% We are done
+ PosPlus = Pos + 1,
+ case Accu of
+ [] ->
+ {Head,Tail} =
+ case is_cr_at(Pos - 1,Bin) of
+ false ->
+ <<H:PosPlus/binary,
+ T/binary>> = Bin,
+ {H,T};
+ true ->
+ PosMinus = Pos - 1,
+ <<H:PosMinus/binary,
+ _,_,T/binary>> = Bin,
+ {binrev([],[H,$\n]),T}
+ end,
+ case Tail of
+ <<>> ->
+ {ok, cast(Head,Enc), queue:tail(Q)};
+ _ ->
+ {ok, cast(Head,Enc),
+ queue:cons(Tail, queue:tail(Q))}
+ end;
+ [<<$\r>>|Stack1] when Pos =:= 0 ->
+ <<_:PosPlus/binary,Tail/binary>> = Bin,
+ case Tail of
+ <<>> ->
+ {ok, cast(binrev(Stack1, [$\n]),Enc),
+ queue:tail(Q)};
+ _ ->
+ {ok, cast(binrev(Stack1, [$\n]),Enc),
+ queue:cons(Tail, queue:tail(Q))}
+ end;
+ _ ->
+ {Head,Tail} =
+ case is_cr_at(Pos - 1,Bin) of
+ false ->
+ <<H:PosPlus/binary,
+ T/binary>> = Bin,
+ {H,T};
+ true ->
+ PosMinus = Pos - 1,
+ <<H:PosMinus/binary,
+ _,_,T/binary>> = Bin,
+ {[H,$\n],T}
+ end,
+ case Tail of
+ <<>> ->
+ {ok, cast(binrev(Accu,[Head]),Enc),
+ queue:tail(Q)};
+ _ ->
+ {ok, cast(binrev(Accu,[Head]),Enc),
+ queue:cons(Tail, queue:tail(Q))}
+ end
+ end
+ end
+ end.
+
+binrev(L, T) ->
+ list_to_binary(lists:reverse(L, T)).
+
+%% Entry function.
+get_chars(Prompt, M, F, Xa, Port, Q, Enc) ->
+ case prompt(Port, Prompt) of
+ error ->
+ {error,{error,get_chars},Q};
+ ok ->
+ case {get(eof),queue:is_empty(Q)} of
+ {true,true} ->
+ {ok,eof,Q};
+ _ ->
+ get_chars(Prompt, M, F, Xa, Port, Q, start, Enc)
+ end
+ end.
+
+%% First loop. Wait for port data. Respond to output requests.
+get_chars(Prompt, M, F, Xa, Port, Q, State, Enc) ->
+ case queue:is_empty(Q) of
+ true ->
+ receive
+ {Port,{data,Bytes}} ->
+ get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc);
+ {Port, eof} ->
+ put(eof, true),
+ {ok, eof, []};
+ {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
+ do_io_request(Req, From, ReplyAs, Port,
+ queue:new()), %Keep Q over this call
+ %% No prompt.
+ get_chars(Prompt, M, F, Xa, Port, Q, State, Enc);
+ {io_request,From,ReplyAs,Request} when is_pid(From) ->
+ get_chars_req(Prompt, M, F, Xa, Port, Q, State,
+ Request, From, ReplyAs, Enc);
+ {'EXIT',From,What} when node(From) =:= node() ->
+ {exit,What}
+ end;
+ false ->
+ get_chars_apply(State, M, F, Xa, Port, Q, Enc)
+ end.
+
+get_chars_req(Prompt, M, F, XtraArg, Port, Q, State,
+ Req, From, ReplyAs, Enc) ->
+ do_io_request(Req, From, ReplyAs, Port, queue:new()), %Keep Q over this call
+ case prompt(Port, Prompt) of
+ error ->
+ {error,{error,get_chars},Q};
+ ok ->
+ get_chars(Prompt, M, F, XtraArg, Port, Q, State, Enc)
+ end.
+
+%% Second loop. Pass data to client as long as it wants more.
+%% A ^G in data interrupts loop if 'noshell' is not undefined.
+get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc) ->
+ case get(shell) of
+ noshell ->
+ get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, Bytes),Enc);
+ _ ->
+ case contains_ctrl_g_or_ctrl_c(Bytes) of
+ false ->
+ get_chars_apply(State, M, F, Xa, Port,
+ queue:snoc(Q, Bytes),Enc);
+ _ ->
+ throw(new_shell)
+ end
+ end.
+
+get_chars_apply(State0, M, F, Xa, Port, Q, Enc) ->
+ case catch M:F(State0, cast(queue:head(Q),Enc), Enc, Xa) of
+ {stop,Result,<<>>} ->
+ {ok,Result,queue:tail(Q)};
+ {stop,Result,[]} ->
+ {ok,Result,queue:tail(Q)};
+ {stop,Result,eof} ->
+ {ok,Result,queue:tail(Q)};
+ {stop,Result,Buf} ->
+ {ok,Result,queue:cons(Buf, queue:tail(Q))};
+ {'EXIT',_Why} ->
+ {error,{error,err_func(M, F, Xa)},queue:new()};
+ State1 ->
+ get_chars_more(State1, M, F, Xa, Port, queue:tail(Q), Enc)
+ end.
+
+get_chars_more(State, M, F, Xa, Port, Q, Enc) ->
+ case queue:is_empty(Q) of
+ true ->
+ case get(eof) of
+ undefined ->
+ receive
+ {Port,{data,Bytes}} ->
+ get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Enc);
+ {Port,eof} ->
+ put(eof, true),
+ get_chars_apply(State, M, F, Xa, Port,
+ queue:snoc(Q, eof), Enc);
+ {'EXIT',From,What} when node(From) =:= node() ->
+ {exit,What}
+ end;
+ _ ->
+ get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, eof), Enc)
+ end;
+ false ->
+ get_chars_apply(State, M, F, Xa, Port, Q, Enc)
+ end.
+
+%% common case, reduces execution time by 20%
+prompt(_Port, '') -> ok;
+prompt(Port, Prompt) ->
+ Encoding = get(encoding),
+ PromptString = io_lib:format_prompt(Prompt, Encoding),
+ case wrap_characters_to_binary(PromptString, unicode, Encoding) of
+ Bin when is_binary(Bin) ->
+ put_port(Bin, Port);
+ error ->
+ error
+ end.
+
+%% Convert error code to make it look as before
+err_func(io_lib, get_until, {_,F,_}) ->
+ F;
+err_func(_, F, _) ->
+ F.
+
+%% using regexp reduces execution time by >50% compared to old code
+%% running two regexps in sequence is much faster than \\x03|\\x07
+contains_ctrl_g_or_ctrl_c(BinOrList)->
+ case {re:run(BinOrList, <<3>>),re:run(BinOrList, <<7>>)} of
+ {nomatch, nomatch} -> false;
+ _ -> true
+ end.
+
+%% Convert a buffer between list and binary
+cast(Data, _Encoding) when is_atom(Data) ->
+ Data;
+cast(Data, Encoding) ->
+ IoEncoding = get(encoding),
+ cast(Data, get(read_mode), IoEncoding, Encoding).
+
+cast(B, binary, latin1, latin1) when is_binary(B) ->
+ B;
+cast(L, binary, latin1, latin1) ->
+ case catch erlang:iolist_to_binary(L) of
+ Bin when is_binary(Bin) -> Bin;
+ _ -> exit({no_translation, latin1, latin1})
+ end;
+cast(Data, binary, unicode, latin1) when is_binary(Data); is_list(Data) ->
+ case catch unicode:characters_to_binary(Data, unicode, latin1) of
+ Bin when is_binary(Bin) -> Bin;
+ _ -> exit({no_translation, unicode, latin1})
+ end;
+cast(Data, binary, latin1, unicode) when is_binary(Data); is_list(Data) ->
+ case catch unicode:characters_to_binary(Data, latin1, unicode) of
+ Bin when is_binary(Bin) -> Bin;
+ _ -> exit({no_translation, latin1, unicode})
+ end;
+cast(B, binary, unicode, unicode) when is_binary(B) ->
+ B;
+cast(L, binary, unicode, unicode) ->
+ case catch unicode:characters_to_binary(L, unicode) of
+ Bin when is_binary(Bin) -> Bin;
+ _ -> exit({no_translation, unicode, unicode})
+ end;
+cast(B, list, latin1, latin1) when is_binary(B) ->
+ binary_to_list(B);
+cast(L, list, latin1, latin1) ->
+ case catch erlang:iolist_to_binary(L) of
+ Bin when is_binary(Bin) -> binary_to_list(Bin);
+ _ -> exit({no_translation, latin1, latin1})
+ end;
+cast(Data, list, unicode, latin1) when is_binary(Data); is_list(Data) ->
+ case catch unicode:characters_to_list(Data, unicode) of
+ Chars when is_list(Chars) ->
+ [ case X of
+ High when High > 255 ->
+ exit({no_translation, unicode, latin1});
+ Low ->
+ Low
+ end || X <- Chars ];
+ _ ->
+ exit({no_translation, unicode, latin1})
+ end;
+cast(Data, list, latin1, unicode) when is_binary(Data); is_list(Data) ->
+ case catch unicode:characters_to_list(Data, latin1) of
+ Chars when is_list(Chars) -> Chars;
+ _ -> exit({no_translation, latin1, unicode})
+ end;
+cast(Data, list, unicode, unicode) when is_binary(Data); is_list(Data) ->
+ case catch unicode:characters_to_list(Data, unicode) of
+ Chars when is_list(Chars) -> Chars;
+ _ -> exit({no_translation, unicode, unicode})
+ end.
+
+wrap_characters_to_binary(Chars, unicode, latin1) ->
+ case catch unicode:characters_to_binary(Chars, unicode, latin1) of
+ Bin when is_binary(Bin) ->
+ Bin;
+ _ ->
+ case catch unicode:characters_to_list(Chars, unicode) of
+ L when is_list(L) ->
+ list_to_binary(
+ [ case X of
+ High when High > 255 ->
+ ["\\x{",erlang:integer_to_list(X, 16),$}];
+ Low ->
+ Low
+ end || X <- L ]);
+ _ ->
+ error
+ end
+ end;
+wrap_characters_to_binary(Bin, From, From) when is_binary(Bin) ->
+ Bin;
+wrap_characters_to_binary(Chars, From, To) ->
+ case catch unicode:characters_to_binary(Chars, From, To) of
+ Bin when is_binary(Bin) ->
+ Bin;
+ _ ->
+ error
+ end.
+
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index d00a46f..56a3940 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -47,6 +47,7 @@
deprecated/4,
indent/1,
update_code/1,
+ update_code/2,
remove_from_code_path/1,
cleanup_code_path/1,
args_to_tasks/1,
@@ -60,13 +61,15 @@
tup_find/2,
line_count/1,
set_httpc_options/0,
+ url_append_path/2,
escape_chars/1,
escape_double_quotes/1,
escape_double_quotes_weak/1,
check_min_otp_version/1,
check_blacklisted_otp_versions/1,
info_useless/2,
- list_dir/1]).
+ list_dir/1,
+ user_agent/0]).
%% for internal use only
-export([otp_release/0]).
@@ -284,7 +287,18 @@ tup_umerge(NewList, OldList) ->
tup_umerge_([], Olds) ->
Olds;
tup_umerge_([New|News], Olds) ->
- lists:reverse(umerge(News, Olds, [], New)).
+ tup_umerge_dedup_(umerge(new, News, Olds, [], New), []).
+
+%% removes 100% identical duplicate elements so that
+%% `[a,{a,b},a,{a,c},a]' returns `[a,{a,b},{a,c}]'.
+%% Operates on a reverted list that gets reversed as part of this pass
+tup_umerge_dedup_([], Acc) ->
+ Acc;
+tup_umerge_dedup_([H|T], Acc) ->
+ case lists:member(H,T) of
+ true -> tup_umerge_dedup_(T, Acc);
+ false -> tup_umerge_dedup_(T, [H|Acc])
+ end.
tup_find(_Elem, []) ->
false;
@@ -300,35 +314,58 @@ tup_find(Elem, [Elem1 | Elems]) when is_tuple(Elem1) ->
tup_find(Elem, [_Elem | Elems]) ->
tup_find(Elem, Elems).
-%% This is equivalent to umerge2_2 in the stdlib, except we use the expanded
-%% value/key only to compare
-umerge(News, [Old|Olds], Merged, Cmp) when element(1, Cmp) == element(1, Old);
- element(1, Cmp) == Old;
- Cmp == element(1, Old);
- Cmp =< Old ->
- umerge(News, Olds, [Cmp | Merged], Cmp, Old);
-umerge(News, [Old|Olds], Merged, Cmp) ->
- umerge(News, Olds, [Old | Merged], Cmp);
-umerge(News, [], Merged, Cmp) ->
- lists:reverse(News, [Cmp | Merged]).
-
-%% Similar to stdlib's umerge2_1 in the stdlib, except that when the expanded
-%% value/keys compare equal, we check if the element is a full dupe to clear it
-%% (like the stdlib function does) or otherwise keep the duplicate around in
-%% an order that prioritizes 'New' elements.
-umerge([New|News], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
- umerge(News, Olds, Merged, New);
-umerge([New|News], Olds, Merged, _CmpMerged, Cmp) when element(1,New) == element(1, Cmp);
- element(1,New) == Cmp;
- New == element(1, Cmp);
- New =< Cmp ->
- umerge(News, Olds, [New | Merged], New, Cmp);
-umerge([New|News], Olds, Merged, _CmpMerged, Cmp) -> % >
- umerge(News, Olds, [Cmp | Merged], New);
-umerge([], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
- lists:reverse(Olds, Merged);
-umerge([], Olds, Merged, _CmpMerged, Cmp) ->
- lists:reverse(Olds, [Cmp | Merged]).
+-spec umerge(new|old, News, Olds, Acc, Current) -> Merged when
+ News :: [term()],
+ Olds :: [term()],
+ Acc :: [term()],
+ Current :: term(),
+ Merged :: [term()].
+umerge(_, [], [], Acc, Current) ->
+ [Current | Acc];
+umerge(new, News, [], Acc, Current) ->
+ %% only news left
+ lists:reverse(News, [Current|Acc]);
+umerge(old, [], Olds, Acc, Current) ->
+ %% only olds left
+ lists:reverse(Olds, [Current|Acc]);
+umerge(new, News, [Old|Olds], Acc, Current) ->
+ {Dir, Merged, NewCurrent} = compare({new, Current}, {old, Old}),
+ umerge(Dir, News, Olds, [Merged|Acc], NewCurrent);
+umerge(old, [New|News], Olds, Acc, Current) ->
+ {Dir, Merged, NewCurrent} = compare({new, New}, {old, Current}),
+ umerge(Dir, News, Olds, [Merged|Acc], NewCurrent).
+
+-spec compare({Priority, term()}, {Secondary, term()}) ->
+ {NextPriority, Merged, Larger} when
+ Priority :: new | old,
+ Secondary :: new | old,
+ NextPriority :: new | old,
+ Merged :: term(),
+ Larger :: term().
+compare({Priority, A}, {Secondary, B}) when is_tuple(A), is_tuple(B) ->
+ KA = element(1,A),
+ KB = element(1,B),
+ if KA == KB -> {Secondary, A, B};
+ KA < KB -> {Secondary, A, B};
+ KA > KB -> {Priority, B, A}
+ end;
+compare({Priority, A}, {Secondary, B}) when not is_tuple(A), not is_tuple(B) ->
+ if A == B -> {Secondary, A, B};
+ A < B -> {Secondary, A, B};
+ A > B -> {Priority, B, A}
+ end;
+compare({Priority, A}, {Secondary, B}) when is_tuple(A), not is_tuple(B) ->
+ KA = element(1,A),
+ if KA == B -> {Secondary, A, B};
+ KA < B -> {Secondary, A, B};
+ KA > B -> {Priority, B, A}
+ end;
+compare({Priority, A}, {Secondary, B}) when not is_tuple(A), is_tuple(B) ->
+ KB = element(1,B),
+ if A == KB -> {Secondary, A, B};
+ A < KB -> {Secondary, A, B};
+ A > KB -> {Priority, B, A}
+ end.
%% Implements wc -l functionality used to determine patchcount from git output
line_count(PatchLines) ->
@@ -371,6 +408,10 @@ abort_if_blacklisted(BlacklistedRegex, OtpRelease) ->
[OtpRelease, BlacklistedRegex])
end.
+user_agent() ->
+ {ok, Vsn} = application:get_key(rebar, vsn),
+ ?FMT("Rebar/~s (OTP/~s)", [Vsn, otp_release()]).
+
%% ====================================================================
%% Internal functions
%% ====================================================================
@@ -644,7 +685,9 @@ indent(Amount) when erlang:is_integer(Amount) ->
%% Replace code paths with new paths for existing apps and
%% purge code of the old modules from those apps.
-update_code(Paths) ->
+update_code(Paths) -> update_code(Paths, []).
+
+update_code(Paths, Opts) ->
lists:foreach(fun(Path) ->
Name = filename:basename(Path, "/ebin"),
App = list_to_atom(Name),
@@ -654,19 +697,18 @@ update_code(Paths) ->
code:add_patha(Path),
ok;
{ok, Modules} ->
- %% stick rebar ebin dir before purging to prevent
- %% inadvertent termination
- RebarBin = code:lib_dir(rebar, ebin),
- ok = code:stick_dir(RebarBin),
%% replace_path causes problems when running
%% tests in projects like erlware_commons that rebar3
%% also includes
%code:replace_path(App, Path),
code:del_path(App),
code:add_patha(Path),
- [begin code:purge(M), code:delete(M) end || M <- Modules],
- %% unstick rebar dir
- ok = code:unstick_dir(RebarBin)
+ case lists:member(soft_purge, Opts) of
+ true ->
+ [begin code:soft_purge(M), code:delete(M) end || M <- Modules];
+ false ->
+ [begin code:purge(M), code:delete(M) end || M <- Modules]
+ end
end
end, Paths).
@@ -762,6 +804,15 @@ set_httpc_options(Scheme, Proxy) ->
{ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy),
httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar).
+url_append_path(Url, ExtraPath) ->
+ case http_uri:parse(Url) of
+ {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
+ {ok, lists:append([atom_to_list(Scheme), "://", UserInfo, Host, ":", integer_to_list(Port),
+ filename:join(Path, ExtraPath), "?", Query])};
+ _ ->
+ error
+ end.
+
%% escape\ as\ a\ shell\?
escape_chars(Str) when is_atom(Str) ->
escape_chars(atom_to_list(Str));
@@ -782,8 +833,11 @@ info_useless(Old, New) ->
not lists:member(Name, New)],
ok.
--ifdef(no_list_dir_all).
-list_dir(Dir) -> file:list_dir(Dir).
--else.
-list_dir(Dir) -> file:list_dir_all(Dir).
--endif.
+list_dir(Dir) ->
+ %% `list_dir_all` returns raw files which are unsupported
+ %% prior to R16 so just fall back to `list_dir` if running
+ %% on an earlier vm
+ case erlang:function_exported(file, list_dir_all, 1) of
+ true -> file:list_dir_all(Dir);
+ false -> file:list_dir(Dir)
+ end.
diff --git a/test/rebar_as_SUITE.erl b/test/rebar_as_SUITE.erl
index 99c7e30..0f37dc8 100644
--- a/test/rebar_as_SUITE.erl
+++ b/test/rebar_as_SUITE.erl
@@ -13,7 +13,8 @@
as_comma_then_space/1,
as_dir_name/1,
as_with_task_args/1,
- warn_on_empty_profile/1]).
+ warn_on_empty_profile/1,
+ clean_as_profile/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -32,7 +33,7 @@ all() -> [as_basic, as_multiple_profiles, as_multiple_tasks,
as_multiple_profiles_multiple_tasks,
as_comma_placement, as_comma_then_space,
as_dir_name, as_with_task_args,
- warn_on_empty_profile].
+ warn_on_empty_profile, clean_as_profile].
as_basic(Config) ->
AppDir = ?config(apps, Config),
@@ -166,3 +167,20 @@ warn_match(App, History) ->
false
end,
History).
+
+clean_as_profile(Config) ->
+ AppDir = ?config(apps, Config),
+
+ Name = rebar_test_utils:create_random_name("clean_as_profile_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ rebar_test_utils:run_and_check(Config,
+ [],
+ ["as", "foo", "compile"],
+ {ok, [{app, Name, valid}]}),
+
+ rebar_test_utils:run_and_check(Config,
+ [],
+ ["clean", "-a", "-p", "foo"],
+ {ok, [{app, Name, invalid}]}).
diff --git a/test/rebar_compile_SUITE.erl b/test/rebar_compile_SUITE.erl
index 1c2c527..76a3de5 100644
--- a/test/rebar_compile_SUITE.erl
+++ b/test/rebar_compile_SUITE.erl
@@ -37,7 +37,10 @@
only_default_transitive_deps/1,
clean_all/1,
override_deps/1,
- profile_override_deps/1]).
+ profile_override_deps/1,
+ deps_build_in_prod/1,
+ include_file_relative_to_working_directory/1,
+ include_file_in_src/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -58,7 +61,8 @@ all() ->
deps_in_path, checkout_priority, highest_version_of_pkg_dep,
parse_transform_test, erl_first_files_test, mib_test,
umbrella_mib_first_test, only_default_transitive_deps,
- clean_all, override_deps, profile_override_deps].
+ clean_all, override_deps, profile_override_deps, deps_build_in_prod,
+ include_file_relative_to_working_directory, include_file_in_src].
groups() ->
[{basic_app, [], [build_basic_app, paths_basic_app, clean_basic_app]},
@@ -89,7 +93,7 @@ init_per_group(basic_app, 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]),
-
+
[{app_names, [Name]}, {vsns, [Vsn]}|NewConfig];
init_per_group(release_apps, Config) ->
@@ -103,7 +107,7 @@ init_per_group(release_apps, Config) ->
Name2 = rebar_test_utils:create_random_name("relapp2_"),
Vsn2 = rebar_test_utils:create_random_vsn(),
rebar_test_utils:create_app(filename:join([AppDir,"apps",Name2]), Name2, Vsn2, [kernel, stdlib]),
-
+
[{app_names, [Name1, Name2]}, {vsns, [Vsn1, Vsn2]}|NewConfig];
init_per_group(checkout_apps, Config) ->
@@ -415,7 +419,7 @@ paths_basic_app(Config) ->
[Vsn] = ?config(vsns, Config),
{ok, State} = rebar_test_utils:run_and_check(Config, [], ["compile"], return),
-
+
code:add_paths(rebar_state:code_paths(State, all_deps)),
ok = application:load(list_to_atom(Name)),
Loaded = application:loaded_applications(),
@@ -1018,11 +1022,14 @@ mib_test(Config) ->
rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}),
- %% check a beam corresponding to the src in the extra src_dir exists in ebin
+ %% check a bin corresponding to the mib in the mibs dir exists in priv/mibs
PrivMibsDir = filename:join([AppDir, "_build", "default", "lib", Name, "priv", "mibs"]),
true = filelib:is_file(filename:join([PrivMibsDir, "SIMPLE-MIB.bin"])),
- %% check the extra src_dir was linked into the _build dir
+ %% check a hrl corresponding to the mib in the mibs dir exists in include
+ true = filelib:is_file(filename:join([AppDir, "include", "SIMPLE-MIB.hrl"])),
+
+ %% check the mibs dir was linked into the _build dir
true = filelib:is_dir(filename:join([AppDir, "_build", "default", "lib", Name, "mibs"])).
umbrella_mib_first_test(Config) ->
@@ -1065,11 +1072,14 @@ umbrella_mib_first_test(Config) ->
rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}),
- %% check a beam corresponding to the src in the extra src_dir exists in ebin
+ %% check a bin corresponding to the mib in the mibs dir exists in priv/mibs
PrivMibsDir = filename:join([AppsDir, "_build", "default", "lib", Name, "priv", "mibs"]),
true = filelib:is_file(filename:join([PrivMibsDir, "SIMPLE-MIB.bin"])),
- %% check the extra src_dir was linked into the _build dir
+ %% check a hrl corresponding to the mib in the mibs dir exists in include
+ true = filelib:is_file(filename:join([AppDir, "include", "SIMPLE-MIB.hrl"])),
+
+ %% check the mibs dir was linked into the _build dir
true = filelib:is_dir(filename:join([AppsDir, "_build", "default", "lib", Name, "mibs"])).
only_default_transitive_deps(Config) ->
@@ -1167,3 +1177,91 @@ profile_override_deps(Config) ->
{ok, [{dep, "some_dep"},{dep_not_exist, "other_dep"}]}
).
+%% verify a deps prod profile is used
+%% tested by checking prod hooks run and outputs to default profile dir for dep
+%% and prod deps are installed for dep
+deps_build_in_prod(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]),
+
+ GitDeps = rebar_test_utils:expand_deps(git, [{"asdf", "1.0.0", []}]),
+ PkgName = rebar_test_utils:create_random_name("pkg1_"),
+ {SrcDeps, _} = rebar_test_utils:flat_deps(GitDeps),
+ mock_git_resource:mock([{deps, SrcDeps},
+ {config, [{profiles, [{prod, [{pre_hooks, [{compile, "echo whatsup > randomfile"}]},
+ {deps, [list_to_atom(PkgName)]}]}]}]}]),
+
+ mock_pkg_resource:mock([{pkgdeps, [{{iolist_to_binary(PkgName), <<"0.1.0">>}, []}]}]),
+
+ Deps = rebar_test_utils:top_level_deps(GitDeps),
+ RConfFile = rebar_test_utils:create_config(AppDir, [{deps, Deps}]),
+ {ok, RConf} = file:consult(RConfFile),
+
+ %% Build with deps.
+ rebar_test_utils:run_and_check(
+ Config, RConf, ["compile"],
+ {ok, [{app, Name}, {dep, "asdf", <<"1.0.0">>}, {dep, PkgName},
+ {file, filename:join([AppDir, "_build", "default", "lib", "asdf", "randomfile"])}]}
+ ).
+
+%% verify that the proper include path is defined
+%% according the erlang doc which states:
+%% If the filename File is absolute (possibly after variable substitution),
+%% the include file with that name is included. Otherwise, the specified file
+%% is searched for in the following directories, and in this order:
+%% * The current working directory
+%% * The directory where the module is being compiled
+%% * The directories given by the include option
+include_file_relative_to_working_directory(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]),
+
+ Src = <<"-module(test).\n"
+"\n"
+"-include(\"include/test.hrl\").\n"
+"\n"
+"test() -> ?TEST_MACRO.\n"
+"\n">>,
+ Include = <<"-define(TEST_MACRO, test).\n">>,
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "src", "dummy"])),
+ ok = file:write_file(filename:join([AppDir, "src", "test.erl"]), Src),
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "include", "dummy"])),
+ ok = file:write_file(filename:join([AppDir, "include", "test.hrl"]), Include),
+
+ RebarConfig = [],
+ rebar_test_utils:run_and_check(Config, RebarConfig,
+ ["compile"],
+ {ok, [{app, Name}]}).
+
+include_file_in_src(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]),
+
+ Src = <<"-module(test).\n"
+"\n"
+"-include(\"test.hrl\").\n"
+"\n"
+"test() -> ?TEST_MACRO.\n"
+"\n">>,
+ Include = <<"-define(TEST_MACRO, test).\n">>,
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "src", "dummy"])),
+ ok = file:write_file(filename:join([AppDir, "src", "test.erl"]), Src),
+
+ ok = file:write_file(filename:join([AppDir, "src", "test.hrl"]), Include),
+
+ RebarConfig = [],
+ rebar_test_utils:run_and_check(Config, RebarConfig,
+ ["compile"],
+ {ok, [{app, Name}]}).
diff --git a/test/rebar_cover_SUITE.erl b/test/rebar_cover_SUITE.erl
index ba078c2..a838d7d 100644
--- a/test/rebar_cover_SUITE.erl
+++ b/test/rebar_cover_SUITE.erl
@@ -72,7 +72,7 @@ basic_extra_src_dirs(Config) ->
Name = rebar_test_utils:create_random_name("cover_extra_"),
Vsn = rebar_test_utils:create_random_vsn(),
- rebar_test_utils:create_eunit_app(AppDir, Name, Vsn, [kernel, stdlib]),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
ExtraSrc = io_lib:format("-module(~ts_extra).\n-export([ok/0]).\nok() -> ok.\n", [Name]),
@@ -86,8 +86,11 @@ basic_extra_src_dirs(Config) ->
["eunit", "--cover"],
{ok, [{app, Name}]}),
- Mod = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name]))),
- {file, _} = cover:is_compiled(Mod).
+ Mod = list_to_atom(Name),
+ {file, _} = cover:is_compiled(Mod),
+
+ ExtraMod = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name]))),
+ {file, _} = cover:is_compiled(ExtraMod).
release_extra_src_dirs(Config) ->
AppDir = ?config(apps, Config),
@@ -120,10 +123,15 @@ release_extra_src_dirs(Config) ->
["eunit", "--cover"],
{ok, [{app, Name1}, {app, Name2}]}),
- Mod1 = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name1]))),
+ Mod1 = list_to_atom(Name1),
{file, _} = cover:is_compiled(Mod1),
- Mod2 = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name2]))),
- {file, _} = cover:is_compiled(Mod2).
+ Mod2 = list_to_atom(Name2),
+ {file, _} = cover:is_compiled(Mod2),
+
+ ExtraMod1 = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name1]))),
+ {file, _} = cover:is_compiled(ExtraMod1),
+ ExtraMod2 = list_to_atom(lists:flatten(io_lib:format("~ts_extra", [Name2]))),
+ {file, _} = cover:is_compiled(ExtraMod2).
root_extra_src_dirs(Config) ->
AppDir = ?config(apps, Config),
@@ -147,6 +155,11 @@ root_extra_src_dirs(Config) ->
["eunit", "--cover"],
{ok, [{app, Name1}, {app, Name2}]}),
+ Mod1 = list_to_atom(Name1),
+ {file, _} = cover:is_compiled(Mod1),
+ Mod2 = list_to_atom(Name2),
+ {file, _} = cover:is_compiled(Mod2),
+
{file, _} = cover:is_compiled(extra).
index_written(Config) ->
diff --git a/test/rebar_ct_SUITE.erl b/test/rebar_ct_SUITE.erl
index cdd3774..94ab690 100644
--- a/test/rebar_ct_SUITE.erl
+++ b/test/rebar_ct_SUITE.erl
@@ -17,15 +17,50 @@
multi_suite/1,
all_suite/1,
single_dir_and_single_suite/1,
- symlinked_dir_overwritten_fix/1,
- data_dir_correct/1]).
+ suite_at_root/1,
+ suite_at_app_root/1,
+ data_dir_correct/1,
+ cmd_label/1,
+ cmd_config/1,
+ cmd_allow_user_terms/1,
+ cmd_logdir/1,
+ cmd_logopts/1,
+ cmd_verbosity/1,
+ cmd_repeat/1,
+ cmd_duration/1,
+ cmd_until/1,
+ cmd_force_stop/1,
+ cmd_basic_html/1,
+ cmd_stylesheet/1,
+ cmd_decrypt_key/1,
+ cmd_decrypt_file/1,
+ cmd_abort_if_missing_suites/1,
+ cmd_multiply_timetraps/1,
+ cmd_scale_timetraps/1,
+ cmd_create_priv_dir/1,
+ cfg_opts/1,
+ cfg_arbitrary_opts/1,
+ cfg_test_spec/1,
+ cfg_atom_suites/1,
+ cover_compiled/1,
+ misspecified_ct_opts/1,
+ misspecified_ct_compile_opts/1,
+ misspecified_ct_first_files/1]).
-include_lib("common_test/include/ct.hrl").
all() -> [{group, basic_app},
{group, multi_app},
{group, dirs_and_suites},
- {group, data_dirs}].
+ {group, data_dirs},
+ {group, ct_opts},
+ {group, cover},
+ cfg_opts, cfg_arbitrary_opts,
+ cfg_test_spec,
+ cfg_atom_suites,
+ misspecified_ct_opts,
+ misspecified_ct_compile_opts,
+ misspecified_ct_first_files].
groups() -> [{basic_app, [], [basic_app_default_dirs,
basic_app_default_beams]},
@@ -40,8 +75,28 @@ groups() -> [{basic_app, [], [basic_app_default_dirs,
multi_suite,
all_suite,
single_dir_and_single_suite,
- symlinked_dir_overwritten_fix]},
- {data_dirs, [], [data_dir_correct]}].
+ suite_at_root,
+ suite_at_app_root]},
+ {data_dirs, [], [data_dir_correct]},
+ {ct_opts, [], [cmd_label,
+ cmd_config,
+ cmd_allow_user_terms,
+ cmd_logdir,
+ cmd_logopts,
+ cmd_verbosity,
+ cmd_repeat,
+ cmd_duration,
+ cmd_until,
+ cmd_force_stop,
+ cmd_basic_html,
+ cmd_stylesheet,
+ cmd_decrypt_key,
+ cmd_decrypt_file,
+ cmd_abort_if_missing_suites,
+ cmd_multiply_timetraps,
+ cmd_scale_timetraps,
+ cmd_create_priv_dir]},
+ {cover, [], [cover_compiled]}].
init_per_group(basic_app, Config) ->
C = rebar_test_utils:init_rebar_state(Config, "ct_"),
@@ -56,22 +111,14 @@ init_per_group(basic_app, Config) ->
ok = filelib:ensure_dir(Suite),
ok = file:write_file(Suite, test_suite(Name)),
- {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "compile"], return),
+ {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return),
- LibDirs = rebar_dir:lib_dirs(State),
- State1 = rebar_app_discover:do(State, LibDirs),
-
- Providers = rebar_state:providers(State1),
- Namespace = rebar_state:namespace(State1),
- CommandProvider = providers:get_provider(ct, Providers, Namespace),
- GetOptSpec = providers:opts(CommandProvider),
- {ok, GetOptResult} = getopt:parse(GetOptSpec, []),
-
- State2 = rebar_state:command_parsed_args(State1, GetOptResult),
+ Tests = rebar_prv_common_test:prepare_tests(State),
+ {ok, NewState} = rebar_prv_common_test:compile(State, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Result = rebar_prv_common_test:setup_ct(State2),
-
- [{result, Result}, {appnames, [Name]}|C];
+ [{result, Opts}, {appnames, [Name]}|C];
init_per_group(multi_app, Config) ->
C = rebar_test_utils:init_rebar_state(Config, "ct_"),
@@ -99,22 +146,14 @@ init_per_group(multi_app, Config) ->
ok = filelib:ensure_dir(Suite3),
ok = file:write_file(Suite3, test_suite("extras")),
- {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "compile"], return),
+ {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return),
- LibDirs = rebar_dir:lib_dirs(State),
- State1 = rebar_app_discover:do(State, LibDirs),
+ Tests = rebar_prv_common_test:prepare_tests(State),
+ {ok, NewState} = rebar_prv_common_test:compile(State, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Providers = rebar_state:providers(State1),
- Namespace = rebar_state:namespace(State1),
- CommandProvider = providers:get_provider(ct, Providers, Namespace),
- GetOptSpec = providers:opts(CommandProvider),
- {ok, GetOptResult} = getopt:parse(GetOptSpec, []),
-
- State2 = rebar_state:command_parsed_args(State1, GetOptResult),
-
- Result = rebar_prv_common_test:setup_ct(State2),
-
- [{result, Result}, {appnames, [Name1, Name2]}|C];
+ [{result, Opts}, {appnames, [Name1, Name2]}|C];
init_per_group(dirs_and_suites, Config) ->
C = rebar_test_utils:init_rebar_state(Config, "ct_"),
@@ -142,7 +181,49 @@ init_per_group(dirs_and_suites, Config) ->
ok = filelib:ensure_dir(Suite3),
ok = file:write_file(Suite3, test_suite("extras")),
- [{appnames, [Name1, Name2]}|C];
+ Suite4 = filename:join([AppDir, "root_SUITE.erl"]),
+ ok = file:write_file(Suite4, test_suite("root")),
+
+ ok = file:write_file(filename:join([AppDir, "root_SUITE.hrl"]), <<>>),
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "root_SUITE_data", "dummy.txt"])),
+ ok = file:write_file(filename:join([AppDir, "root_SUITE_data", "some_data.txt"]), <<>>),
+
+ Suite5 = filename:join([AppDir, "apps", Name2, "app_root_SUITE.erl"]),
+ ok = file:write_file(Suite5, test_suite("app_root")),
+
+ ok = file:write_file(filename:join([AppDir, "apps", Name2, "app_root_SUITE.hrl"]), <<>>),
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "apps", Name2, "app_root_SUITE_data", "dummy.txt"])),
+ ok = file:write_file(filename:join([AppDir, "apps", Name2, "app_root_SUITE_data", "some_data.txt"]), <<>>),
+
+ {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return),
+
+ [{s, State}, {appnames, [Name1, Name2]}|C];
+init_per_group(ct_opts, Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_opts"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_opts_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return),
+
+ [{result, State}|C];
+init_per_group(cover, Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_opts"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_opts_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ {ok, State} = rebar_test_utils:run_and_check(C, [], ["as", "test", "lock"], return),
+
+ [{result, State}, {name, Name}|C];
init_per_group(_, Config) -> Config.
end_per_group(_Group, _Config) -> ok.
@@ -152,10 +233,10 @@ basic_app_default_dirs(Config) ->
[Name] = ?config(appnames, Config),
Result = ?config(result, Config),
- Expect = filename:absname(filename:join([AppDir, "_build", "test", "lib", Name, "test"])),
+ Expect = filename:join([AppDir, "_build", "test", "lib", Name, "test"]),
Dir = proplists:get_value(dir, Result),
- Expect = Dir.
+ [Expect] = Dir.
basic_app_default_beams(Config) ->
AppDir = ?config(apps, Config),
@@ -178,7 +259,7 @@ multi_app_default_dirs(Config) ->
Expect1 = filename:absname(filename:join([AppDir, "_build", "test", "lib", Name1, "test"])),
Expect2 = filename:absname(filename:join([AppDir, "_build", "test", "lib", Name2, "test"])),
- Expect3 = filename:absname(filename:join([AppDir, "_build", "test", "test"])),
+ Expect3 = filename:absname(filename:join([AppDir, "_build", "test", "extras", "test"])),
Dirs = proplists:get_value(dir, Result),
true = (lists:sort([Expect1, Expect2, Expect3]) == lists:sort(Dirs)).
@@ -215,8 +296,7 @@ multi_app_default_beams(Config) ->
single_app_dir(Config) ->
AppDir = ?config(apps, Config),
[Name1, _Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -233,17 +313,19 @@ single_app_dir(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = filename:absname(filename:join([AppDir, "_build", "test", "lib", Name1, "test"])),
- Dir = proplists:get_value(dir, Result),
+ Expect = filename:join([AppDir, "_build", "test", "lib", Name1, "test"]),
+ Dir = proplists:get_value(dir, Opts),
- Expect = Dir.
+ [Expect] = Dir.
single_extra_dir(Config) ->
AppDir = ?config(apps, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -257,22 +339,24 @@ single_extra_dir(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = filename:absname(filename:join([AppDir, "_build", "test", "test"])),
- Dir = proplists:get_value(dir, Result),
+ Expect = filename:join([AppDir, "_build", "test", "extras", "test"]),
+ Dir = proplists:get_value(dir, Opts),
- Expect = Dir.
+ [Expect] = Dir.
single_unmanaged_dir(Config) ->
PrivDir = ?config(priv_dir, Config),
+ State = ?config(s, Config),
Suite = filename:join([PrivDir, "unmanaged_dir", "unmanaged_dir_SUITE.erl"]),
ok = filelib:ensure_dir(Suite),
ok = file:write_file(Suite, test_suite("unmanaged_dir")),
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
-
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -285,18 +369,20 @@ single_unmanaged_dir(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = filename:absname(filename:join([PrivDir, "unmanaged_dir"])),
- Dir = proplists:get_value(dir, Result),
+ Expect = filename:join([PrivDir, "unmanaged_dir"]),
+ Dir = proplists:get_value(dir, Opts),
- Expect = Dir.
+ [Expect] = Dir.
single_suite(Config) ->
AppDir = ?config(apps, Config),
[Name1, _Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -314,24 +400,26 @@ single_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = [filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "lib",
- Name1,
- "test",
- Name1 ++ "_SUITE"]))],
- Suite = proplists:get_value(suite, Result),
+ Expect = filename:join([AppDir,
+ "_build",
+ "test",
+ "lib",
+ Name1,
+ "test",
+ Name1 ++ "_SUITE"]),
+ Suite = proplists:get_value(suite, Opts),
- Expect = Suite.
+ [Expect] = Suite.
single_extra_suite(Config) ->
AppDir = ?config(apps, Config),
[_Name1, _Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -347,27 +435,30 @@ single_extra_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = [filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "test",
- "extra_SUITE"]))],
- Suite = proplists:get_value(suite, Result),
+ Expect = filename:join([AppDir,
+ "_build",
+ "test",
+ "extras",
+ "test",
+ "extra_SUITE"]),
+ Suite = proplists:get_value(suite, Opts),
- Expect = Suite.
+ [Expect] = Suite.
single_unmanaged_suite(Config) ->
PrivDir = ?config(priv_dir, Config),
[_Name1, _Name2] = ?config(appnames, Config),
+ State = ?config(s, Config),
Suite = filename:join([PrivDir, "unmanaged", "unmanaged_SUITE.erl"]),
ok = filelib:ensure_dir(Suite),
ok = file:write_file(Suite, test_suite("unmanaged")),
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
-
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -382,20 +473,22 @@ single_unmanaged_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = [filename:absname(filename:join([PrivDir,
- "unmanaged",
- "unmanaged_SUITE"]))],
- SuitePath = proplists:get_value(suite, Result),
+ Expect = filename:join([PrivDir,
+ "unmanaged",
+ "unmanaged_SUITE"]),
+ SuitePath = proplists:get_value(suite, Opts),
- Expect = SuitePath.
+ [Expect] = SuitePath.
multi_suite(Config) ->
AppDir = ?config(apps, Config),
[Name1, Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -417,31 +510,33 @@ multi_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
-
- Expect1 = filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "lib",
- Name1,
- "test",
- Name1 ++ "_SUITE"])),
- Expect2 = filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "lib",
- Name2,
- "test",
- Name2 ++ "_SUITE"])),
- Suites = proplists:get_value(suite, Result),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
+
+ Expect1 = filename:join([AppDir,
+ "_build",
+ "test",
+ "lib",
+ Name1,
+ "test",
+ Name1 ++ "_SUITE"]),
+ Expect2 = filename:join([AppDir,
+ "_build",
+ "test",
+ "lib",
+ Name2,
+ "test",
+ Name2 ++ "_SUITE"]),
+ Suites = proplists:get_value(suite, Opts),
true = (lists:sort([Expect1, Expect2]) == lists:sort(Suites)).
all_suite(Config) ->
AppDir = ?config(apps, Config),
[Name1, Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -465,36 +560,39 @@ all_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
-
- Expect1 = filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "lib",
- Name1,
- "test",
- Name1 ++ "_SUITE"])),
- Expect2 = filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "lib",
- Name2,
- "test",
- Name2 ++ "_SUITE"])),
- Expect3 = filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "test",
- "extra_SUITE"])),
- Suites = proplists:get_value(suite, Result),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
+
+ Expect1 = filename:join([AppDir,
+ "_build",
+ "test",
+ "lib",
+ Name1,
+ "test",
+ Name1 ++ "_SUITE"]),
+ Expect2 = filename:join([AppDir,
+ "_build",
+ "test",
+ "lib",
+ Name2,
+ "test",
+ Name2 ++ "_SUITE"]),
+ Expect3 = filename:join([AppDir,
+ "_build",
+ "test",
+ "extras",
+ "test",
+ "extra_SUITE"]),
+ Suites = proplists:get_value(suite, Opts),
true = (lists:sort([Expect1, Expect2, Expect3]) == lists:sort(Suites)).
single_dir_and_single_suite(Config) ->
AppDir = ?config(apps, Config),
[_Name1, _Name2] = ?config(appnames, Config),
-
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -509,22 +607,62 @@ single_dir_and_single_suite(Config) ->
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = [filename:absname(filename:join([AppDir,
- "_build",
- "test",
- "test",
- "extra_SUITE"]))],
- Suite = proplists:get_value(suite, Result),
+ Expect = filename:join([AppDir,
+ "_build",
+ "test",
+ "extras",
+ "test"]),
+ Dir = proplists:get_value(dir, Opts),
+ [Expect] = Dir,
- Expect = Suite.
+ Suite = proplists:get_value(suite, Opts),
+ ["extra_SUITE"] = Suite.
-symlinked_dir_overwritten_fix(Config) ->
+suite_at_root(Config) ->
AppDir = ?config(apps, Config),
- [Name1, _Name2] = ?config(appnames, Config),
+ State = ?config(s, Config),
+
+ LibDirs = rebar_dir:lib_dirs(State),
+ State1 = rebar_app_discover:do(State, LibDirs),
+
+ Providers = rebar_state:providers(State1),
+ Namespace = rebar_state:namespace(State1),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--suite=" ++ filename:join([AppDir, "root_SUITE"])]),
+
+ State2 = rebar_state:command_parsed_args(State1, GetOptResult),
+
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
+
+ Suite = proplists:get_value(suite, Opts),
+ Expected = filename:join([AppDir, "_build", "test", "extras", "root_SUITE"]),
+ [Expected] = Suite,
+
+ TestHrl = filename:join([AppDir, "_build", "test", "extras", "root_SUITE.hrl"]),
+ true = filelib:is_file(TestHrl),
+
+ TestBeam = filename:join([AppDir, "_build", "test", "extras", "root_SUITE.beam"]),
+ true = filelib:is_file(TestBeam),
+
+ DataDir = filename:join([AppDir, "_build", "test", "extras", "root_SUITE_data"]),
+ true = filelib:is_dir(DataDir),
+
+ DataFile = filename:join([AppDir, "_build", "test", "extras", "root_SUITE_data", "some_data.txt"]),
+ true = filelib:is_file(DataFile).
- {ok, State} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return),
+suite_at_app_root(Config) ->
+ AppDir = ?config(apps, Config),
+ [_Name1, Name2] = ?config(appnames, Config),
+ State = ?config(s, Config),
LibDirs = rebar_dir:lib_dirs(State),
State1 = rebar_app_discover:do(State, LibDirs),
@@ -533,28 +671,450 @@ symlinked_dir_overwritten_fix(Config) ->
Namespace = rebar_state:namespace(State1),
CommandProvider = providers:get_provider(ct, Providers, Namespace),
GetOptSpec = providers:opts(CommandProvider),
- {ok, GetOptResult} = getopt:parse(GetOptSpec,
- ["--dir=" ++ filename:join([AppDir,
- "apps",
- Name1])]),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--suite=" ++ filename:join([AppDir, "apps", Name2, "app_root_SUITE"])]),
State2 = rebar_state:command_parsed_args(State1, GetOptResult),
- Result = rebar_prv_common_test:setup_ct(State2),
+ Tests = rebar_prv_common_test:prepare_tests(State2),
+ {ok, NewState} = rebar_prv_common_test:compile(State2, Tests),
+ {ok, T} = Tests,
+ Opts = rebar_prv_common_test:translate_paths(NewState, T),
- Expect = filename:absname(filename:join([AppDir, "_build", "test", "lib", Name1])),
- Dir = proplists:get_value(dir, Result),
+ Suite = proplists:get_value(suite, Opts),
+ Expected = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE"]),
+ [Expected] = Suite,
- Expect = Dir,
+ TestHrl = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE.hrl"]),
+ true = filelib:is_file(TestHrl),
- {ok, _} = rebar_test_utils:run_and_check(Config, [], ["as", "test", "compile"], return).
+ TestBeam = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE.beam"]),
+ true = filelib:is_file(TestBeam),
+
+ DataDir = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE_data"]),
+ true = filelib:is_dir(DataDir),
+
+ DataFile = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE_data", "some_data.txt"]),
+ true = filelib:is_file(DataFile).
%% this test probably only fails when this suite is run via rebar3 with the --cover flag
data_dir_correct(Config) ->
DataDir = ?config(data_dir, Config),
Parts = filename:split(DataDir),
+ ct:pal(Parts),
["rebar_ct_SUITE_data","test","rebar","lib","test","_build"|_] = lists:reverse(Parts).
+cmd_label(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--label=this_is_a_label"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({label, "this_is_a_label"}, TestOpts).
+
+cmd_config(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--config=config/foo,config/bar,config/baz"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({config, ["config/foo", "config/bar", "config/baz"]}, TestOpts).
+
+cmd_allow_user_terms(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--allow_user_terms=true"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({allow_user_terms, true}, TestOpts).
+
+cmd_logdir(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--logdir=/tmp/ct_logs"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({logdir, "/tmp/ct_logs"}, TestOpts).
+
+cmd_logopts(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--logopts=no_src,no_nl"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({logopts, [no_src, no_nl]}, TestOpts).
+
+cmd_verbosity(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--verbosity=43"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({verbosity, 43}, TestOpts).
+
+cmd_repeat(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--repeat=3"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({repeat, 3}, TestOpts).
+
+cmd_duration(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--duration=001500"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({duration, "001500"}, TestOpts).
+
+cmd_until(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--until=001500"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({until, "001500"}, TestOpts).
+
+cmd_force_stop(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--force_stop=skip_rest"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({force_stop, skip_rest}, TestOpts).
+
+cmd_basic_html(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--basic_html"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({basic_html, true}, TestOpts).
+
+cmd_stylesheet(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--stylesheet=resources/tests.css"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({stylesheet, "resources/tests.css"}, TestOpts).
+
+cmd_decrypt_key(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--decrypt_key==ac467e30"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({decrypt_key, "=ac467e30"}, TestOpts).
+
+cmd_decrypt_file(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--decrypt_file=../keyfile.pem"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({decrypt_file, "../keyfile.pem"}, TestOpts).
+
+cmd_abort_if_missing_suites(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--abort_if_missing_suites"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({abort_if_missing_suites, true}, TestOpts).
+
+cmd_multiply_timetraps(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--multiply_timetraps=3"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({multiply_timetraps, 3}, TestOpts).
+
+cmd_scale_timetraps(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--scale_timetraps"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({scale_timetraps, true}, TestOpts).
+
+cmd_create_priv_dir(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--create_priv_dir=manual_per_tc"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(NewState),
+
+ true = lists:member({create_priv_dir, manual_per_tc}, TestOpts).
+
+cfg_opts(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_opts_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_opts_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_opts, [{label, "this_is_a_label"}, {decrypt_file, "../keyfile.pem"}]}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State),
+
+ true = lists:member({label, "this_is_a_label"}, TestOpts),
+ true = lists:member({decrypt_file, "../keyfile.pem"}, TestOpts).
+
+%% allow even nonsensical opts to be passed to ct_run for futureproofing
+cfg_arbitrary_opts(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_arbitrary_opts_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_arbitrary_opts_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_opts, [{foo, 1}, {bar, 2}, {baz, 3}]}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State),
+
+ true = lists:member({foo, 1}, TestOpts),
+ true = lists:member({bar, 2}, TestOpts),
+ true = lists:member({baz, 3}, TestOpts).
+
+cfg_test_spec(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_test_spec_opts_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_test_spec_opts_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_opts, [{test_spec, "spec/foo.spec"}]}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ {error, {rebar_prv_common_test, Error}} = rebar_prv_common_test:prepare_tests(State),
+
+ {badconfig, "Test specs not supported"} = Error.
+
+cfg_atom_suites(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_atom_suites_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_atom_suites_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_opts, [{suite, [foo, bar, baz]}]}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ {ok, TestOpts} = rebar_prv_common_test:prepare_tests(State),
+
+ true = lists:member({suite, ["foo", "bar", "baz"]}, TestOpts).
+
+cover_compiled(Config) ->
+ State = ?config(result, Config),
+
+ Providers = rebar_state:providers(State),
+ Namespace = rebar_state:namespace(State),
+ CommandProvider = providers:get_provider(ct, Providers, Namespace),
+ GetOptSpec = providers:opts(CommandProvider),
+ {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--cover"]),
+
+ NewState = rebar_state:command_parsed_args(State, GetOptResult),
+
+ Tests = rebar_prv_common_test:prepare_tests(NewState),
+ {ok, _} = rebar_prv_common_test:compile(NewState, Tests),
+
+ Name = ?config(name, Config),
+ Mod = list_to_atom(Name),
+ {file, _} = cover:is_compiled(Mod).
+
+misspecified_ct_opts(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_atom_suites_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_atom_suites_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_opts, {basic_html, false}}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ {error, {rebar_prv_common_test, Error}} = rebar_prv_common_test:prepare_tests(State),
+
+ {badconfig, {"Value `~p' of option `~p' must be a list", {{basic_html, false}, ct_opts}}} = Error.
+
+misspecified_ct_compile_opts(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_atom_suites_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_atom_suites_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_compile_opts, {d, whatever}}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ Tests = rebar_prv_common_test:prepare_tests(State),
+ {error, {rebar_prv_common_test, Error}} = rebar_prv_common_test:compile(State, Tests),
+
+ {badconfig, {"Value `~p' of option `~p' must be a list", {{d, whatever}, ct_compile_opts}}} = Error.
+
+misspecified_ct_first_files(Config) ->
+ C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_atom_suites_"),
+
+ AppDir = ?config(apps, C),
+
+ Name = rebar_test_utils:create_random_name("ct_cfg_atom_suites_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RebarConfig = [{ct_first_files, some_file}],
+
+ {ok, State} = rebar_test_utils:run_and_check(C, RebarConfig, ["as", "test", "lock"], return),
+
+ Tests = rebar_prv_common_test:prepare_tests(State),
+ {error, {rebar_prv_common_test, Error}} = rebar_prv_common_test:compile(State, Tests),
+
+ {badconfig, {"Value `~p' of option `~p' must be a list", {some_file, ct_first_files}}} = Error.
%% helper for generating test data
test_suite(Name) ->
diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl
index 31e02d9..22a4894 100644
--- a/test/rebar_dialyzer_SUITE.erl
+++ b/test/rebar_dialyzer_SUITE.erl
@@ -69,7 +69,16 @@ update_base_plt(Config) ->
?assertEqual(ErtsFiles, BasePltFiles2),
{ok, PltFiles} = plt_files(Plt),
- ?assertEqual(ErtsFiles, PltFiles).
+ ?assertEqual(ErtsFiles, PltFiles),
+
+ add_missing_file(BasePlt),
+ ok = file:delete(Plt),
+
+ rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer"],
+ {ok, [{app, Name}]}),
+
+ {ok, BasePltFiles3} = plt_files(BasePlt),
+ ?assertEqual(ErtsFiles, BasePltFiles3).
update_app_plt(Config) ->
@@ -103,7 +112,15 @@ update_app_plt(Config) ->
{ok, [{app, Name}]}),
{ok, PltFiles3} = plt_files(Plt),
- ?assertEqual(ErtsFiles, PltFiles3).
+ ?assertEqual(ErtsFiles, PltFiles3),
+
+ add_missing_file(Plt),
+
+ rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer"],
+ {ok, [{app, Name}]}),
+
+ {ok, PltFiles4} = plt_files(Plt),
+ ?assertEqual(ErtsFiles, PltFiles4).
build_release_plt(Config) ->
AppDir = ?config(apps, Config),
@@ -211,6 +228,19 @@ alter_plt(Plt) ->
{files, [code:which(dialyzer)]}]),
ok.
+add_missing_file(Plt) ->
+ Source = code:which(dialyzer),
+ Dest = filename:join(filename:dirname(Plt), "dialyzer.beam"),
+ {ok, _} = file:copy(Source, Dest),
+ _ = try
+ dialyzer:run([{analysis_type, plt_add},
+ {init_plt, Plt},
+ {files, [Dest]}])
+ after
+ ok = file:delete(Dest)
+ end,
+ ok.
+
-spec merge_config(Config, Config) -> Config when
Config :: [{term(), term()}].
merge_config(NewConfig, OldConfig) ->
diff --git a/test/rebar_dir_SUITE.erl b/test/rebar_dir_SUITE.erl
index 526f827..1221db7 100644
--- a/test/rebar_dir_SUITE.erl
+++ b/test/rebar_dir_SUITE.erl
@@ -5,7 +5,7 @@
-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]).
--export([retarget_path/1]).
+-export([retarget_path/1, alt_base_dir_abs/1, alt_base_dir_rel/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -15,7 +15,7 @@
all() -> [default_src_dirs, default_extra_src_dirs, default_all_src_dirs,
src_dirs, extra_src_dirs, all_src_dirs,
profile_src_dirs, profile_extra_src_dirs, profile_all_src_dirs,
- retarget_path].
+ retarget_path, alt_base_dir_abs, alt_base_dir_rel].
init_per_testcase(_, Config) ->
C = rebar_test_utils:init_rebar_state(Config),
@@ -124,4 +124,41 @@ retarget_path(Config) ->
?assertEqual(filename:join([BaseDir, "some_other_dir"]),
rebar_dir:retarget_path(State, filename:join([rebar_dir:root_dir(State), "some_other_dir"]))),
?assertEqual("/somewhere/outside/the/project",
- rebar_dir:retarget_path(State, "/somewhere/outside/the/project")). \ No newline at end of file
+ rebar_dir:retarget_path(State, "/somewhere/outside/the/project")).
+
+alt_base_dir_abs(Config) ->
+ AltName = lists:flatten(io_lib:format("~p", [os:timestamp()])),
+ AltBaseDir = filename:join(?config(priv_dir, Config), AltName),
+ RebarConfig = [{base_dir, AltBaseDir}],
+ {ok, State} = rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], return),
+
+ BaseDir = rebar_dir:base_dir(State),
+ ?assertEqual(filename:join(AltBaseDir, "default"), BaseDir),
+
+ Name1 = ?config(app_one, Config),
+ Name2 = ?config(app_two, Config),
+
+ ?assert(filelib:is_dir(filename:join([BaseDir, "lib", Name1, "ebin"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name1, "ebin", Name1++".app"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name1, "ebin", Name1++".beam"]))),
+ ?assert(filelib:is_dir(filename:join([BaseDir, "lib", Name2, "ebin"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".app"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".beam"]))).
+
+alt_base_dir_rel(Config) ->
+ AltName = lists:flatten(io_lib:format("~p", [os:timestamp()])),
+ AltBaseDir = filename:join("..", AltName),
+ RebarConfig = [{base_dir, AltBaseDir}],
+ {ok, State} = rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], return),
+
+ BaseDir = rebar_dir:base_dir(State),
+
+ Name1 = ?config(app_one, Config),
+ Name2 = ?config(app_two, Config),
+
+ ?assert(filelib:is_dir(filename:join([BaseDir, "lib", Name1, "ebin"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name1, "ebin", Name1++".app"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name1, "ebin", Name1++".beam"]))),
+ ?assert(filelib:is_dir(filename:join([BaseDir, "lib", Name2, "ebin"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".app"]))),
+ ?assert(filelib:is_file(filename:join([BaseDir, "lib", Name2, "ebin", Name2++".beam"]))).
diff --git a/test/rebar_eunit_SUITE.erl b/test/rebar_eunit_SUITE.erl
index 609be51..cb2c911 100644
--- a/test/rebar_eunit_SUITE.erl
+++ b/test/rebar_eunit_SUITE.erl
@@ -11,13 +11,19 @@
-export([single_file_arg/1, multi_file_arg/1, missing_file_arg/1]).
-export([single_dir_arg/1, multi_dir_arg/1, missing_dir_arg/1]).
-export([multiple_arg_composition/1, multiple_arg_errors/1]).
+-export([misspecified_eunit_tests/1]).
+-export([misspecified_eunit_compile_opts/1]).
+-export([misspecified_eunit_first_files/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
all() ->
- [{group, basic_app}, {group, multi_app}, {group, cmd_line_args}].
+ [{group, basic_app}, {group, multi_app}, {group, cmd_line_args},
+ misspecified_eunit_tests,
+ misspecified_eunit_compile_opts,
+ misspecified_eunit_first_files].
groups() ->
[{basic_app, [sequence], [basic_app_compiles, {group, basic_app_results}]},
@@ -36,13 +42,10 @@ groups() ->
init_per_suite(Config) ->
PrivDir = ?config(priv_dir, Config),
DataDir = ?config(data_dir, Config),
- {ok, Cwd} = file:get_cwd(),
- file:set_cwd(PrivDir),
ok = ec_file:copy(filename:join([DataDir, "basic_app.zip"]), filename:join([PrivDir, "basic_app.zip"])),
- {ok, _} = zip:extract(filename:join([PrivDir, "basic_app.zip"])),
+ {ok, _} = zip:extract(filename:join([PrivDir, "basic_app.zip"]), [{cwd, PrivDir}]),
ok = ec_file:copy(filename:join([DataDir, "multi_app.zip"]), filename:join([PrivDir, "multi_app.zip"])),
- {ok, _} = zip:extract(filename:join([PrivDir, "multi_app.zip"])),
- file:set_cwd(Cwd),
+ {ok, _} = zip:extract(filename:join([PrivDir, "multi_app.zip"]), [{cwd, PrivDir}]),
Config.
init_per_group(basic_app, Config) ->
@@ -153,7 +156,9 @@ basic_app_exports(_Config) ->
basic_app_testset(Config) ->
Result = ?config(result, Config),
- {ok, [{application, basic_app}]} = rebar_prv_eunit:prepare_tests(Result).
+ Set = {ok, [{application, basic_app},
+ {module, basic_app_tests_helper}]},
+ Set = rebar_prv_eunit:prepare_tests(Result).
@@ -205,12 +210,14 @@ multi_app_exports(_Config) ->
%% check that the correct tests are schedule to run for project
multi_app_testset(Config) ->
- AppDir = ?config(apps, Config),
Result = ?config(result, Config),
- Set = {ok, [{application, multi_app_bar},
- {application, multi_app_baz},
- {dir, filename:join([AppDir, "test"])}]},
+ Set = {ok, [{application, multi_app_baz},
+ {application, multi_app_bar},
+ {module, multi_app_bar_tests_helper},
+ {module, multi_app_baz_tests_helper},
+ {module, multi_app_tests},
+ {module, multi_app_tests_helper}]},
Set = rebar_prv_eunit:prepare_tests(Result).
@@ -268,7 +275,7 @@ missing_application_arg(Config) ->
State = rebar_state:command_parsed_args(S, Args),
Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Application `missing_app' not found in project."]}}},
- Error = rebar_prv_eunit:prepare_tests(State).
+ Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)).
%% check that the --module cmd line opt generates the correct test set
single_module_arg(Config) ->
@@ -311,8 +318,11 @@ missing_module_arg(Config) ->
{ok, Args} = getopt:parse(rebar_prv_eunit:eunit_opts(S), ["--module=missing_app"]),
State = rebar_state:command_parsed_args(S, Args),
+ T = rebar_prv_eunit:prepare_tests(State),
+ Tests = rebar_prv_eunit:validate_tests(S, T),
+
Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Module `missing_app' not found in project."]}}},
- Error = rebar_prv_eunit:prepare_tests(State).
+ Error = Tests.
%% check that the --suite cmd line opt generates the correct test set
single_suite_arg(Config) ->
@@ -356,7 +366,7 @@ missing_suite_arg(Config) ->
State = rebar_state:command_parsed_args(S, Args),
Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Module `missing_app' not found in project."]}}},
- Error = rebar_prv_eunit:prepare_tests(State).
+ Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)).
%% check that the --file cmd line opt generates the correct test set
single_file_arg(Config) ->
@@ -390,7 +400,7 @@ missing_file_arg(Config) ->
State = rebar_state:command_parsed_args(S, Args),
Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["File `" ++ Path ++"' not found."]}}},
- Error = rebar_prv_eunit:prepare_tests(State).
+ Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)).
%% check that the --dir cmd line opt generates the correct test set
single_dir_arg(Config) ->
@@ -424,7 +434,7 @@ missing_dir_arg(Config) ->
State = rebar_state:command_parsed_args(S, Args),
Error = {error, {rebar_prv_eunit, {eunit_test_errors, ["Directory `" ++ Path ++"' not found."]}}},
- Error = rebar_prv_eunit:prepare_tests(State).
+ Error = rebar_prv_eunit:validate_tests(State, rebar_prv_eunit:prepare_tests(State)).
%% check that multiple args are composed
multiple_arg_composition(Config) ->
@@ -470,11 +480,71 @@ multiple_arg_errors(Config) ->
"--dir=" ++ DirPath]),
State = rebar_state:command_parsed_args(S, Args),
+ T = rebar_prv_eunit:prepare_tests(State),
+ Tests = rebar_prv_eunit:validate_tests(S, T),
+
Expect = ["Application `missing_app' not found in project.",
"Directory `" ++ DirPath ++ "' not found.",
"File `" ++ FilePath ++ "' not found.",
"Module `missing_app' not found in project.",
"Module `missing_app' not found in project."],
- {error, {rebar_prv_eunit, {eunit_test_errors, Expect}}} = rebar_prv_eunit:prepare_tests(State).
+ {error, {rebar_prv_eunit, {eunit_test_errors, Expect}}} = Tests.
+
+misspecified_eunit_tests(Config) ->
+ State = rebar_test_utils:init_rebar_state(Config, "basic_app_"),
+
+ AppDir = ?config(apps, State),
+ PrivDir = ?config(priv_dir, State),
+
+ AppDirs = ["src", "include", "test"],
+
+ lists:foreach(fun(F) -> ec_file:copy(filename:join([PrivDir, "basic_app", F]),
+ filename:join([AppDir, F]),
+ [recursive]) end, AppDirs),
+
+ BaseConfig = [{erl_opts, [{d, config_define}]}, {eunit_compile_opts, [{d, eunit_compile_define}]}],
+
+ RebarConfig = [{eunit_tests, {dir, "test"}}|BaseConfig],
+
+ {error, {rebar_prv_eunit, Error}} = rebar_test_utils:run_and_check(State, RebarConfig, ["eunit"], return),
+
+ {badconfig, {"Value `~p' of option `~p' must be a list", {{dir, "test"}, eunit_tests}}} = Error.
+
+misspecified_eunit_compile_opts(Config) ->
+ State = rebar_test_utils:init_rebar_state(Config, "basic_app_"),
+
+ AppDir = ?config(apps, State),
+ PrivDir = ?config(priv_dir, State),
+
+ AppDirs = ["src", "include", "test"],
+
+ lists:foreach(fun(F) -> ec_file:copy(filename:join([PrivDir, "basic_app", F]),
+ filename:join([AppDir, F]),
+ [recursive]) end, AppDirs),
+
+ RebarConfig = [{erl_opts, [{d, config_define}]}, {eunit_compile_opts, {d, eunit_compile_define}}],
+
+ {error, {rebar_prv_eunit, Error}} = rebar_test_utils:run_and_check(State, RebarConfig, ["eunit"], return),
+
+ {badconfig, {"Value `~p' of option `~p' must be a list", {{d, eunit_compile_define}, eunit_compile_opts}}} = Error.
+
+misspecified_eunit_first_files(Config) ->
+ State = rebar_test_utils:init_rebar_state(Config, "basic_app_"),
+
+ AppDir = ?config(apps, State),
+ PrivDir = ?config(priv_dir, State),
+
+ AppDirs = ["src", "include", "test"],
+
+ lists:foreach(fun(F) -> ec_file:copy(filename:join([PrivDir, "basic_app", F]),
+ filename:join([AppDir, F]),
+ [recursive]) end, AppDirs),
+
+ BaseConfig = [{erl_opts, [{d, config_define}]}, {eunit_compile_opts, [{d, eunit_compile_define}]}],
+
+ RebarConfig = [{eunit_first_files, some_file}|BaseConfig],
+
+ {error, {rebar_prv_eunit, Error}} = rebar_test_utils:run_and_check(State, RebarConfig, ["eunit"], return),
+ {badconfig, {"Value `~p' of option `~p' must be a list", {some_file, eunit_first_files}}} = Error.
diff --git a/test/rebar_file_utils_SUITE.erl b/test/rebar_file_utils_SUITE.erl
index a061325..c1f85b3 100644
--- a/test/rebar_file_utils_SUITE.erl
+++ b/test/rebar_file_utils_SUITE.erl
@@ -97,10 +97,17 @@ path_from_ancestor(_Config) ->
?assertEqual({error, badparent}, rebar_file_utils:path_from_ancestor("/foo/bar/baz", "/foo/bar/baz/qux")).
canonical_path(_Config) ->
- ?assertEqual(filename:nativename("/"), rebar_file_utils:canonical_path("/")),
- ?assertEqual(filename:nativename("/"), rebar_file_utils:canonical_path("/../../..")),
- ?assertEqual("/foo", rebar_file_utils:canonical_path("/foo/bar/..")),
- ?assertEqual("/foo", rebar_file_utils:canonical_path("/foo/../foo")),
- ?assertEqual("/foo", rebar_file_utils:canonical_path("/foo/.")),
- ?assertEqual("/foo", rebar_file_utils:canonical_path("/foo/./.")),
- ?assertEqual("/foo/bar", rebar_file_utils:canonical_path("/foo/./bar")). \ No newline at end of file
+ %% We find the root so that the name works both on unix-likes and
+ %% with Windows.
+ Root = case os:type() of
+ {win32, _} -> filename:nativename(filename:absname("/")); % C:\, with proper drive
+ _ -> "/"
+ end,
+ ?assertEqual(filename:nativename(Root), rebar_file_utils:canonical_path("/")),
+ ?assertEqual(filename:nativename(Root), rebar_file_utils:canonical_path("/../../..")),
+ ?assertEqual(Root ++ "foo", rebar_file_utils:canonical_path("/foo/bar/..")),
+ ?assertEqual(Root ++ "foo", rebar_file_utils:canonical_path("/foo/../foo")),
+ ?assertEqual(Root ++ "foo", rebar_file_utils:canonical_path("/foo/.")),
+ ?assertEqual(Root ++ "foo", rebar_file_utils:canonical_path("/foo/./.")),
+ ?assertEqual(filename:nativename(Root ++ "foo/bar"),
+ rebar_file_utils:canonical_path("/foo/./bar")).
diff --git a/test/rebar_new_SUITE.erl b/test/rebar_new_SUITE.erl
index 3cee6f2..1971be6 100644
--- a/test/rebar_new_SUITE.erl
+++ b/test/rebar_new_SUITE.erl
@@ -6,9 +6,26 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-all() -> [app_git_user, app_hg_user, app_with_fallbacks].
+all() -> [app_git_user, app_hg_user, app_with_fallbacks,
+ app_with_flags1, app_with_flags2, plugin_tpl].
+init_per_testcase(plugin_tpl, Config) ->
+ application:load(rebar),
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Name = rebar_test_utils:create_random_name("plugin_tpl"),
+ AppsDir = filename:join([PrivDir, rebar_test_utils:create_random_name(Name)]),
+ ec_file:copy(filename:join([DataDir, "plugin_tpl"]), AppsDir, [recursive]),
+ Verbosity = rebar3:log_level(),
+ rebar_log:init(command_line, Verbosity),
+ GlobalDir = filename:join([DataDir, "cache"]),
+ State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}
+ ,{global_rebar_dir, GlobalDir}
+ ,{root_dir, AppsDir}]),
+ mock_home_dir(DataDir),
+ mock_empty_escript_templates(),
+ [{apps, AppsDir}, {state, State}, {name, Name} | Config];
init_per_testcase(Case, Config0) ->
Config = rebar_test_utils:init_rebar_state(Config0),
Name = rebar_test_utils:create_random_name(atom_to_list(Case)),
@@ -95,11 +112,60 @@ app_hg_user(Config) ->
{filename:join(["src", Name++"_app.erl"]), [Name]}
]).
+app_with_flags1(Config) ->
+ Name = ?config(name, Config),
+ rebar_test_utils:run_and_check(
+ Config, [],
+ ["new", "test_app", "-f", Name],
+ {ok, []}
+ ),
+ validate_files(
+ Config, Name,
+ [{"LICENSE", []},
+ {"README.md", []},
+ {".gitignore", []},
+ {"rebar.config", []},
+ {filename:join(["src", Name++".app.src"]), [Name]},
+ {filename:join(["src", Name++"_sup.erl"]), [Name]},
+ {filename:join(["src", Name++"_app.erl"]), [Name]}
+ ]).
+
+app_with_flags2(Config) ->
+ Name = ?config(name, Config),
+ rebar_test_utils:run_and_check(
+ Config, [],
+ ["new", "-f", "test_app", Name],
+ {ok, []}
+ ),
+ validate_files(
+ Config, Name,
+ [{"LICENSE", []},
+ {"README.md", []},
+ {".gitignore", []},
+ {"rebar.config", []},
+ {filename:join(["src", Name++".app.src"]), [Name]},
+ {filename:join(["src", Name++"_sup.erl"]), [Name]},
+ {filename:join(["src", Name++"_app.erl"]), [Name]}
+ ]).
+
+plugin_tpl(Config) ->
+ Name = ?config(name, Config),
+ rebar_test_utils:run_and_check(
+ Config, [],
+ ["new", "-f", "tpl", Name],
+ {ok, []}
+ ),
+ Result = filename:join(["src", Name++".erl"]), % In CWD
+ {ok, Bin} = file:read_file(Result),
+ {match, _} = re:run(Bin, Name, [multiline,global]).
+
validate_files(_Config, Name, Checks) ->
[begin
Path = filename:join([Name, File]),
+ ct:pal("validating ~s for content", [Path]),
{ok, Bin} = file:read_file(Path),
[{match, _} = re:run(Bin, Pattern, [multiline,global])
|| Pattern <- Patterns]
end || {File, Patterns} <- Checks],
ok.
+
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/LICENSE.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/LICENSE.dtl
new file mode 100644
index 0000000..41588ab
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/LICENSE.dtl
@@ -0,0 +1,29 @@
+Copyright (c) {{copyright_year}}, {{author_name}} <{{author_email}}>.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/README.md.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/README.md.dtl
new file mode 100644
index 0000000..5507536
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/README.md.dtl
@@ -0,0 +1,9 @@
+{{name}}
+=====
+
+{{desc}}
+
+Build
+-----
+
+ $ rebar3 compile
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/app.erl.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/app.erl.dtl
new file mode 100644
index 0000000..83eb9a3
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/app.erl.dtl
@@ -0,0 +1,27 @@
+%%%-------------------------------------------------------------------
+%% @doc {{name}} public API
+%% @end
+%%%-------------------------------------------------------------------
+
+-module({{name}}_app).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2
+ ,stop/1]).
+
+%%====================================================================
+%% API
+%%====================================================================
+
+start(_StartType, _StartArgs) ->
+ {{name}}_sup:start_link().
+
+%%--------------------------------------------------------------------
+stop(_State) ->
+ ok.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/bad_index.template b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/bad_index.template
new file mode 100644
index 0000000..50998cc
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/bad_index.template
@@ -0,0 +1,13 @@
+{description, "OTP Application"}.
+{variables, [
+ {name, "mylib", "Name of the OTP application"},
+ {desc, "An OTP application", "Short description of the app"}
+]}.
+bad_term,
+{template, "app.erl.dtl", "{{name}}/src/{{name}}_app.erl"}.
+{template, "sup.erl.dtl", "{{name}}/src/{{name}}_sup.erl"}.
+{template, "otp_app.app.src.dtl", "{{name}}/src/{{name}}.app.src"}.
+{template, "rebar.config.dtl", "{{name}}/rebar.config"}.
+{template, "gitignore.dtl", "{{name}}/.gitignore"}.
+{template, "LICENSE.dtl", "{{name}}/LICENSE"}.
+{template, "README.md.dtl", "{{name}}/README.md"}.
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/gitignore.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/gitignore.dtl
new file mode 100644
index 0000000..40a1d4f
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/gitignore.dtl
@@ -0,0 +1,18 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+_rel
+_deps
+_plugins
+_tdeps
+logs
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/otp_app.app.src.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/otp_app.app.src.dtl
new file mode 100644
index 0000000..5188f56
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/otp_app.app.src.dtl
@@ -0,0 +1,12 @@
+{application, {{name}},
+ [{description, "{{desc}}"}
+ ,{vsn, "0.1.0"}
+ ,{registered, []}
+ ,{mod, {'{{name}}_app', []}}
+ ,{applications,
+ [kernel
+ ,stdlib
+ ]}
+ ,{env,[]}
+ ,{modules, []}
+ ]}.
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/rebar.config.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/rebar.config.dtl
new file mode 100644
index 0000000..f618f3e
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/rebar.config.dtl
@@ -0,0 +1,2 @@
+{erl_opts, [debug_info]}.
+{deps, []}. \ No newline at end of file
diff --git a/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/sup.erl.dtl b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/sup.erl.dtl
new file mode 100644
index 0000000..a2e7209
--- /dev/null
+++ b/test/rebar_new_SUITE_data/.rebar3/templates/bad_index/sup.erl.dtl
@@ -0,0 +1,35 @@
+%%%-------------------------------------------------------------------
+%% @doc {{name}} top level supervisor.
+%% @end
+%%%-------------------------------------------------------------------
+
+-module({{name}}_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+%%====================================================================
+%% API functions
+%%====================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%%====================================================================
+%% Supervisor callbacks
+%%====================================================================
+
+%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
+init([]) ->
+ {ok, { {one_for_all, 0, 1}, []} }.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/.gitignore b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/.gitignore
new file mode 100644
index 0000000..a939dce
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/.gitignore
@@ -0,0 +1,19 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+_rel
+_deps
+_plugins
+_tdeps
+logs
+_build \ No newline at end of file
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/module.erl.dtl b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/module.erl.dtl
new file mode 100644
index 0000000..9129961
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/module.erl.dtl
@@ -0,0 +1,2 @@
+-module({{name}}).
+
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/tpl.template b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/tpl.template
new file mode 100644
index 0000000..7fa4caf
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/priv/tpl.template
@@ -0,0 +1,7 @@
+{description, "A basic template"}.
+{variables, [
+ {name, "mod", "Name of the module"}
+]}.
+
+{dir, "test"}.
+{template, "module.erl.dtl", "src/{{name}}.erl"}.
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/rebar.config b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/rebar.config
new file mode 100644
index 0000000..f618f3e
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/rebar.config
@@ -0,0 +1,2 @@
+{erl_opts, [debug_info]}.
+{deps, []}. \ No newline at end of file
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.app.src b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.app.src
new file mode 100644
index 0000000..6c6d811
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.app.src
@@ -0,0 +1,15 @@
+{application, 'tpl',
+ [{description, "A rebar plugin"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {contributors, []},
+ {licenses, []},
+ {links, []}
+ ]}.
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.erl b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.erl
new file mode 100644
index 0000000..529bcb8
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl.erl
@@ -0,0 +1,8 @@
+-module('tpl').
+
+-export([init/1]).
+
+-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
+init(State) ->
+ {ok, State1} = 'tpl_prv':init(State),
+ {ok, State1}.
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl_prv.erl b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl_prv.erl
new file mode 100644
index 0000000..c68ffa3
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/_checkouts/tpl/src/tpl_prv.erl
@@ -0,0 +1,32 @@
+-module('tpl_prv').
+
+-export([init/1, do/1, format_error/1]).
+
+-define(PROVIDER, 'tpl').
+-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 tpl"}, % How to use the plugin
+ {opts, []}, % list of options understood by the plugin
+ {short_desc, "A rebar plugin"},
+ {desc, "A rebar plugin"}
+ ]),
+ {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/test/rebar_new_SUITE_data/plugin_tpl/rebar.config b/test/rebar_new_SUITE_data/plugin_tpl/rebar.config
new file mode 100644
index 0000000..74d1fc5
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/rebar.config
@@ -0,0 +1,3 @@
+{erl_opts, [debug_info]}.
+{deps, []}.
+{plugins, [tpl]}.
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.app.src b/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.app.src
new file mode 100644
index 0000000..8f18874
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.app.src
@@ -0,0 +1,15 @@
+{application, 'plugin_tpl',
+ [{description, "An OTP library"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {contributors, []},
+ {licenses, []},
+ {links, []}
+ ]}.
diff --git a/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.erl b/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.erl
new file mode 100644
index 0000000..406bd97
--- /dev/null
+++ b/test/rebar_new_SUITE_data/plugin_tpl/src/plugin_tpl.erl
@@ -0,0 +1,13 @@
+-module('plugin_tpl').
+
+%% API exports
+-export([]).
+
+%%====================================================================
+%% API functions
+%%====================================================================
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl
index b3201ad..9f19e0d 100644
--- a/test/rebar_pkg_SUITE.erl
+++ b/test/rebar_pkg_SUITE.erl
@@ -11,7 +11,8 @@
-define(good_checksum, <<"1C6CE379D191FBAB41B7905075E0BF87CBBE23C77CECE775C5A0B786B2244C35">>).
all() -> [good_uncached, good_cached, badindexchk, badpkg,
- bad_to_good, good_disconnect, bad_disconnect, pkgs_provider].
+ bad_to_good, good_disconnect, bad_disconnect, pkgs_provider,
+ find_highest_matching].
init_per_suite(Config) ->
application:start(meck),
@@ -20,7 +21,19 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
application:stop(meck).
-init_per_testcase(pkgs_provider, Config) ->
+init_per_testcase(pkgs_provider=Name, Config) ->
+ %% Need to mock out a registry for this test now because it will try to update it automatically
+ Priv = ?config(priv_dir, Config),
+ Tid = ets:new(registry_table, [public]),
+ ets:insert_new(Tid, []),
+ CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]),
+ CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]),
+ filelib:ensure_dir(filename:join([CacheDir, "registry"])),
+ ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])),
+ meck:new(rebar_packages, [passthrough]),
+ meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
+ meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
+ rebar_prv_update:hex_to_index(rebar_state:new()),
Config;
init_per_testcase(good_uncached=Name, Config0) ->
Config = [{good_cache, false},
@@ -74,10 +87,13 @@ init_per_testcase(bad_disconnect=Name, Config0) ->
meck:unload(httpc),
meck:new(httpc, [passthrough, unsticky]),
meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end),
- Config.
-
-end_per_testcase(pkgs_provider, Config) ->
Config;
+init_per_testcase(Name, Config0) ->
+ Config = [{good_cache, false},
+ {pkg, {<<"goodpkg">>, <<"1.0.0">>}}
+ | Config0],
+ mock_config(Name, Config).
+
end_per_testcase(_, Config) ->
unmock_config(Config),
Config.
@@ -162,6 +178,16 @@ pkgs_provider(Config) ->
{ok, []}
).
+find_highest_matching(_Config) ->
+ State = rebar_state:new(),
+ {ok, Vsn} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"1.0.0">>, package_index, State),
+ ?assertEqual(<<"1.0.1">>, Vsn),
+ {ok, Vsn1} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"1.0">>, package_index, State),
+ ?assertEqual(<<"1.1.1">>, Vsn1),
+ {ok, Vsn2} = rebar_packages:find_highest_matching(<<"goodpkg">>, <<"2.0">>, package_index, State),
+ ?assertEqual(<<"2.0.0">>, Vsn2).
+
+
%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
@@ -172,10 +198,13 @@ mock_config(Name, Config) ->
Tid = ets:new(registry_table, [public]),
ets:insert_new(Tid, [
{<<"badindexchk">>,[[<<"1.0.0">>]]},
- {<<"goodpkg">>,[[<<"1.0.0">>]]},
+ {<<"goodpkg">>,[[<<"1.0.0">>, <<"1.0.1">>, <<"1.1.1">>, <<"2.0.0">>]]},
{<<"badpkg">>,[[<<"1.0.0">>]]},
{{<<"badindexchk">>,<<"1.0.0">>}, [[], ?bad_checksum, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]},
+ {{<<"goodpkg">>,<<"1.0.1">>}, [[], ?good_checksum, [<<"rebar3">>]]},
+ {{<<"goodpkg">>,<<"1.1.1">>}, [[], ?good_checksum, [<<"rebar3">>]]},
+ {{<<"goodpkg">>,<<"2.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]},
{{<<"badpkg">>,<<"1.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]}
]),
CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]),
@@ -193,8 +222,8 @@ mock_config(Name, Config) ->
meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end),
meck:new(rebar_packages, [passthrough]),
- meck:expect(rebar_packages, registry_dir, fun(_) -> CacheDir end),
- meck:expect(rebar_packages, package_dir, fun(_) -> CacheDir end),
+ meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
+ meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
rebar_prv_update:hex_to_index(rebar_state:new()),
%% Cache fetches are mocked -- we assume the server and clients are
diff --git a/test/rebar_pkg_alias_SUITE.erl b/test/rebar_pkg_alias_SUITE.erl
index f7fa5d4..fef2310 100644
--- a/test/rebar_pkg_alias_SUITE.erl
+++ b/test/rebar_pkg_alias_SUITE.erl
@@ -98,8 +98,8 @@ mock_config(Name, Config) ->
meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end),
meck:new(rebar_packages, [passthrough, no_link]),
- meck:expect(rebar_packages, registry_dir, fun(_) -> CacheDir end),
- meck:expect(rebar_packages, package_dir, fun(_) -> CacheDir end),
+ meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
+ meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
rebar_prv_update:hex_to_index(rebar_state:new()),
%% Cache fetches are mocked -- we assume the server and clients are
diff --git a/test/rebar_plugins_SUITE.erl b/test/rebar_plugins_SUITE.erl
index 3df3c0e..c1a98de 100644
--- a/test/rebar_plugins_SUITE.erl
+++ b/test/rebar_plugins_SUITE.erl
@@ -10,7 +10,9 @@
compile_global_plugins/1,
complex_plugins/1,
list/1,
- upgrade/1]).
+ upgrade/1,
+ sub_app_plugins/1,
+ sub_app_plugin_overrides/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -32,7 +34,7 @@ end_per_testcase(_, _Config) ->
catch meck:unload().
all() ->
- [compile_plugins, compile_global_plugins, complex_plugins, list, upgrade].
+ [compile_plugins, compile_global_plugins, complex_plugins, list, upgrade, sub_app_plugins, sub_app_plugin_overrides].
%% Tests that compiling a project installs and compiles the plugins of deps
compile_plugins(Config) ->
@@ -208,3 +210,74 @@ upgrade(Config) ->
Config, RConf, ["plugins", "upgrade", PkgName],
{ok, [{app, Name}, {plugin, PkgName, <<"0.1.3">>}]}
).
+
+sub_app_plugins(Config) ->
+ AppDir = ?config(apps, Config),
+
+ Name = rebar_test_utils:create_random_name("sub_app1_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+
+ DepName = rebar_test_utils:create_random_name("dep1_"),
+ PluginName = rebar_test_utils:create_random_name("plugin1_"),
+
+ mock_pkg_resource:mock([{pkgdeps, [{{list_to_binary(DepName), list_to_binary(Vsn)}, []},
+ {{list_to_binary(PluginName), list_to_binary(Vsn)}, []}]}]),
+
+ SubAppsDir = filename:join([AppDir, "apps", Name]),
+
+ rebar_test_utils:create_app(SubAppsDir, Name, Vsn, [kernel, stdlib]),
+ rebar_test_utils:create_config(SubAppsDir, [{deps, [{list_to_binary(DepName), list_to_binary(Vsn)}]},
+ {plugins, [list_to_atom(PluginName)]}]),
+
+ 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}, {dep, DepName}, {plugin, PluginName}]}
+ ).
+
+%% Tests that overrides in a dep that includes a plugin are applied to plugin fetching
+sub_app_plugin_overrides(Config) ->
+ AppDir = ?config(apps, Config),
+
+ Name = rebar_test_utils:create_random_name("sub_app1_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ Dep2Name = rebar_test_utils:create_random_name("dep2_"),
+
+ DepName = rebar_test_utils:create_random_name("dep1_"),
+ PluginName = rebar_test_utils:create_random_name("plugin1_"),
+ Vsn2 = rebar_test_utils:create_random_vsn(),
+
+ Deps = rebar_test_utils:expand_deps(git, [{PluginName, Vsn, [{DepName, Vsn, []}]},
+ {DepName, Vsn, []}]),
+ {SrcDeps, _} = rebar_test_utils:flat_deps(Deps),
+ mock_git_resource:mock([{deps, SrcDeps}]),
+
+ mock_pkg_resource:mock([{pkgdeps, [{{list_to_binary(Dep2Name), list_to_binary(Vsn)}, []}]},
+ {config, [{plugins, [{list_to_atom(PluginName),
+ {git, "http://site.com/user/"++PluginName++".git",
+ {tag, Vsn}}}]},
+ %% Dep2 overrides the plugin's deps to have vsn2 of dep1
+ {overrides, [{override, list_to_atom(PluginName),
+ [{deps, [{list_to_atom(DepName),
+ {git, "http://site.com/user/"++DepName++".git",
+ {tag, Vsn2}}}]}]}]}]}]),
+
+ SubAppsDir = filename:join([AppDir, "apps", Name]),
+
+ rebar_test_utils:create_app(SubAppsDir, Name, Vsn, [kernel, stdlib]),
+
+ RConfFile = rebar_test_utils:create_config(AppDir, [{deps, [{list_to_binary(Dep2Name), list_to_binary(Vsn)}]}]),
+ {ok, RConf} = file:consult(RConfFile),
+
+ %% Build with deps.
+ rebar_test_utils:run_and_check(
+ Config, RConf, ["compile"],
+ {ok, [{app, Name}, {dep, Dep2Name, Vsn}, {plugin, DepName, Vsn2}, {plugin, PluginName}]}
+ ).
diff --git a/test/rebar_release_SUITE.erl b/test/rebar_release_SUITE.erl
index f6fe8ff..1125a7e 100644
--- a/test/rebar_release_SUITE.erl
+++ b/test/rebar_release_SUITE.erl
@@ -4,11 +4,14 @@
-include_lib("eunit/include/eunit.hrl").
all() -> [release,
- dev_mode_release,
- profile_dev_mode_override_release,
- tar,
- extend_release,
- user_output_dir].
+ dev_mode_release,
+ profile_dev_mode_override_release,
+ tar,
+ profile_ordering_sys_config_extend,
+ profile_ordering_sys_config_extend_3_tuple_merge,
+ extend_release,
+ user_output_dir, profile_overlays,
+ overlay_vars].
init_per_testcase(Case, Config0) ->
Config = rebar_test_utils:init_rebar_state(Config0),
@@ -111,6 +114,63 @@ extend_release(Config) ->
{ok, [{release, extended, Vsn, false}]}
).
+%% Ensure proper ordering of sys_config and extended releases in profiles
+profile_ordering_sys_config_extend(Config) ->
+ AppDir = ?config(apps, Config),
+ Name = ?config(name, Config),
+ Vsn = "1.0.0",
+ TestSysConfig = filename:join(AppDir, "test.config"),
+ OtherSysConfig = filename:join(AppDir, "other.config"),
+ ok = file:write_file(TestSysConfig, "[]."),
+ ok = file:write_file(OtherSysConfig, "[{some, content}]."),
+ {ok, RebarConfig} =
+ file:consult(rebar_test_utils:create_config(AppDir,
+ [{relx, [{release, {list_to_atom(Name), Vsn},
+ [list_to_atom(Name)]},
+ {sys_config, OtherSysConfig},
+ {lib_dirs, [AppDir]}]},
+ {profiles, [{extended,
+ [{relx, [
+ {sys_config, TestSysConfig}]}]}]}])),
+ rebar_test_utils:run_and_check(
+ Config, RebarConfig,
+ ["as", "extended", "release"],
+ {ok, [{release, list_to_atom(Name), Vsn, false}]}
+ ),
+
+ ReleaseDir = filename:join([AppDir, "./_build/extended/rel/", Name, "releases", Vsn]),
+ {ok, [[]]} = file:consult(filename:join(ReleaseDir, "sys.config")).
+
+%% test that tup_umerge works with tuples of different sizes
+profile_ordering_sys_config_extend_3_tuple_merge(Config) ->
+ AppDir = ?config(apps, Config),
+ Name = ?config(name, Config),
+ Vsn = "1.0.0",
+ TestSysConfig = filename:join(AppDir, "test.config"),
+ OtherSysConfig = filename:join(AppDir, "other.config"),
+ ok = file:write_file(TestSysConfig, "[]."),
+ ok = file:write_file(OtherSysConfig, "[{some, content}]."),
+ {ok, RebarConfig} =
+ file:consult(rebar_test_utils:create_config(AppDir,
+ [{relx, [{release, {list_to_atom(Name), Vsn},
+ [list_to_atom(Name)]},
+ {sys_config, OtherSysConfig},
+ {lib_dirs, [AppDir]}]},
+ {profiles, [{extended,
+ [{relx, [
+ {release, {extended, Vsn, {extend, list_to_atom(Name)}},
+ []},
+ {sys_config, TestSysConfig}]}]}]}])),
+
+ rebar_test_utils:run_and_check(
+ Config, RebarConfig,
+ ["as", "extended", "release", "-n", Name],
+ {ok, [{release, list_to_atom(Name), Vsn, false}]}
+ ),
+
+ ReleaseDir = filename:join([AppDir, "./_build/extended/rel/", Name, "releases", Vsn]),
+ {ok, [[]]} = file:consult(filename:join(ReleaseDir, "sys.config")).
+
user_output_dir(Config) ->
AppDir = ?config(apps, Config),
Name = ?config(name, Config),
@@ -134,3 +194,79 @@ user_output_dir(Config) ->
{ok, RelxState2} = rlx_prv_app_discover:do(RelxState1),
{ok, RelxState3} = rlx_prv_rel_discover:do(RelxState2),
rlx_state:get_realized_release(RelxState3, list_to_atom(Name), Vsn).
+
+profile_overlays(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)]},
+ {overlay, [{mkdir, "randomdir"}]},
+ {lib_dirs, [AppDir]}]},
+ {profiles, [{prod, [{relx, [{overlay, [{mkdir, "otherrandomdir"}]}]}]}]}])),
+
+ ReleaseDir = filename:join([AppDir, "./_build/prod/rel/", Name]),
+
+ rebar_test_utils:run_and_check(
+ Config, RebarConfig,
+ ["as", "prod", "release"],
+ {ok, [{release, list_to_atom(Name), Vsn, false},
+ {dir, filename:join(ReleaseDir, "otherrandomdir")},
+ {dir, filename:join(ReleaseDir, "randomdir")}]}
+ ).
+
+overlay_vars(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)]},
+ {overlay, [
+ {template, filename:join([AppDir, "config/app.config"]),
+ "releases/{{release_version}}/sys.config"}
+ ]},
+ {overlay_vars, filename:join([AppDir, "config/vars.config"])},
+ {lib_dirs, [AppDir]}]}
+ ])),
+
+ ok = filelib:ensure_dir(filename:join([AppDir, "config", "dummy"])),
+
+ OverlayVars = [{var_int, 1},
+ {var_string, "\"test\""},
+ {var_bin_string, "<<\"test\">>"},
+ {var_tuple, "{t, ['atom']}"},
+ {var_list, "[a, b, c, 'd']"},
+ {var_bin, "<<23, 24, 25>>"}],
+ rebar_test_utils:create_config(AppDir,
+ filename:join([AppDir, "config", "vars.config"]),
+ OverlayVars),
+
+ AppConfig = [[{var_int, {{var_int}}},
+ {var_string, {{{var_string}}}},
+ {var_bin_string, {{{var_bin_string}}}},
+ {var_tuple, {{{var_tuple}}}},
+ {var_list, {{{var_list}}}},
+ {var_bin, {{{var_bin}}}}]],
+ rebar_test_utils:create_config(AppDir,
+ filename:join([AppDir, "config", "app.config"]),
+ AppConfig),
+
+ rebar_test_utils:run_and_check(
+ Config, RebarConfig,
+ ["release"],
+ {ok, [{release, list_to_atom(Name), Vsn, false}]}),
+
+ %% now consult the sys.config file to make sure that is has the expected
+ %% format
+ ExpectedSysconfig = [{var_int, 1},
+ {var_string, "test"},
+ {var_bin_string, <<"test">>},
+ {var_tuple, {t, ['atom']}},
+ {var_list, [a, b, c, 'd']},
+ {var_bin, <<23, 24, 25>>}],
+ {ok, [ExpectedSysconfig]} = file:consult(filename:join([AppDir, "_build/default/rel",
+ Name, "releases", Vsn, "sys.config"])).
diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl
index 3943db7..5187bda 100644
--- a/test/rebar_test_utils.erl
+++ b/test/rebar_test_utils.erl
@@ -3,8 +3,8 @@
-include_lib("eunit/include/eunit.hrl").
-export([init_rebar_state/1, init_rebar_state/2, run_and_check/4, check_results/3]).
-export([expand_deps/2, flat_deps/1, top_level_deps/1]).
--export([create_app/4, create_eunit_app/4, create_empty_app/4, create_config/2,
- package_app/3]).
+-export([create_app/4, create_eunit_app/4, create_empty_app/4,
+ create_config/2, create_config/3, package_app/3]).
-export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
%%%%%%%%%%%%%%
@@ -104,11 +104,14 @@ create_empty_app(AppDir, Name, Vsn, Deps) ->
%% each of which will be dumped as a consult file. For example, the list
%% `[a, b, c]' will return the consult file `a. b. c.'.
create_config(AppDir, Contents) ->
- Conf = filename:join([AppDir, "rebar.config"]),
- ok = filelib:ensure_dir(Conf),
+ ConfFilename = filename:join([AppDir, "rebar.config"]),
+ create_config(AppDir, ConfFilename, Contents).
+
+create_config(_AppDir, ConfFilename, Contents) ->
+ ok = filelib:ensure_dir(ConfFilename),
Config = lists:flatten([io_lib:fwrite("~p.~n", [Term]) || Term <- Contents]),
- ok = ec_file:write(Conf, Config),
- Conf.
+ ok = ec_file:write(ConfFilename, Config),
+ ConfFilename.
%% @doc Util to create a random variation of a given name.
create_random_name(Name) ->
diff --git a/test/rebar_utils_SUITE.erl b/test/rebar_utils_SUITE.erl
index 24e8afe..b32992d 100644
--- a/test/rebar_utils_SUITE.erl
+++ b/test/rebar_utils_SUITE.erl
@@ -30,7 +30,8 @@
invalid_otp_version/1,
nonblacklisted_otp_version/1,
blacklisted_otp_version/1,
- sh_does_not_miss_messages/1]).
+ sh_does_not_miss_messages/1,
+ tup_merge/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -44,7 +45,8 @@ end_per_testcase(_, _Config) ->
all() ->
[{group, args_to_tasks},
- sh_does_not_miss_messages].
+ sh_does_not_miss_messages,
+ tup_merge].
groups() ->
[{args_to_tasks, [], [empty_arglist,
@@ -198,3 +200,75 @@ sh_does_not_miss_messages(_Config) ->
false
end,
AnyMessageRemained = false.
+
+tup_merge(_Config) ->
+ ?assertEqual(
+ [a,{a,a},{a,a,a},{a,b},{a,b,b},b,{b,a},{b,a,a},{b,b},{b,b,b},z,{z,a},{z,a,a},{z,b},{z,b,b}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([a,{a,a},{a,a,a},b,{b,a},{b,a,a},z,{z,a},{z,a,a}]),
+ rebar_utils:tup_sort([a,{a,b},{a,b,b},b,{b,b},{b,b,b},z,{z,b},{z,b,b}])
+ )
+ ),
+ ?assertEqual(
+ [a,{a,b},{a,b,b},{a,a},{a,a,a},b,{b,b},{b,b,b},{b,a},{b,a,a},z,{z,b},{z,b,b},{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([a,{a,b},{a,b,b},b,{b,b},{b,b,b},z,{z,b},{z,b,b}]),
+ rebar_utils:tup_sort([a,{a,a},{a,a,a},b,{b,a},{b,a,a},z,{z,a},{z,a,a}])
+ )
+ ),
+ ?assertEqual(
+ [a,{a,b},{a,b,b},{a,a},{a,a,a},b,{b,b},{b,b,b},{b,a},{b,a,a},z,{z,b},{z,b,b},{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([a,b,z,{a,b},{b,b},{z,b},{a,b,b},{b,b,b},{z,b,b}]),
+ rebar_utils:tup_sort([a,{a,a},{a,a,a},b,{b,a},{b,a,a},z,{z,a},{z,a,a}])
+ )
+ ),
+ ?assertEqual(
+ [{a,b},a,{a,b,b},{a,a},{a,a,a},{b,b},b,{b,b,b},{b,a},{b,a,a},{z,b},z,{z,b,b},{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{a,b},{b,b},{z,b},a,b,z,{a,b,b},{b,b,b},{z,b,b}]),
+ rebar_utils:tup_sort([a,{a,a},{a,a,a},b,{b,a},{b,a,a},z,{z,a},{z,a,a}])
+ )
+ ),
+ ?assertEqual(
+ [a,{a,b},{a,b,b},{a,a},{a,a,a},b,{b,b},{b,b,b},{b,a},{b,a,a},z,{z,b},{z,b,b},{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([a,{a,b},{a,b,b},b,{b,b},{b,b,b},z,{z,b},{z,b,b}]),
+ rebar_utils:tup_sort([{a,a},a,{a,a,a},{b,a},b,{b,a,a},{z,a},z,{z,a,a}])
+ )
+ ),
+ ?assertEqual(
+ [{a,b},a,{a,b,b},{a,a},{a,a,a},{b,b},b,{b,b,b},{b,a},{b,a,a},{z,b},z,{z,b,b},{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{a,b},{b,b},{z,b},a,b,z,{a,b,b},{b,b,b},{z,b,b}]),
+ rebar_utils:tup_sort([{a,a},a,{a,a,a},{b,a},b,{b,a,a},{z,a},z,{z,a,a}])
+ )
+ ),
+ ?assertEqual(
+ [{a,b},{a,b,b},a,{a,a},{a,a,a},{b,b},{b,b,b},b,{b,a},{b,a,a},{z,b},{z,b,b},z,{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{a,b},{a,b,b},{b,b},{b,b,b},{z,b},{z,b,b},a,b,z]),
+ rebar_utils:tup_sort([{a,a},{a,a,a},a,{b,a},{b,a,a},b,{z,a},{z,a,a},z])
+ )
+ ),
+ ?assertEqual(
+ [{a,b},{a,b,b},a,{a,a},{a,a,a},{b,b},{b,b,b},b,{b,a},{b,a,a},{z,b},{z,b,b},z,{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{a,b},{a,b,b},{b,b},{b,b,b},{z,b},{z,b,b},a,b,z]),
+ rebar_utils:tup_sort([{a,a},{a,b},{a,a,a},{a,b,b},a,{b,a},{b,a,a},b,{z,a},{z,a,a},z])
+ )
+ ),
+ ?assertEqual(
+ [{l, a}, {r, a, b}, {s, a}, {s, b}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{r, a, b}, {s, a}, {l, a}]),
+ rebar_utils:tup_sort([{s, b}])
+ )
+ ),
+ ?assertEqual(
+ [{a,b,b},{a,b},a,{a,a},{a,a,a},{b,b},{b,b,b},b,{b,a,a},{b,a},{z,b},{z,b,b},z,{z,a},{z,a,a}],
+ rebar_utils:tup_umerge(
+ rebar_utils:tup_sort([{a,b,b},{b,b},{a,b},{b,b,b},{z,b},{z,b,b},a,b,z]),
+ rebar_utils:tup_sort([{a,a},{a,a,a},a,{b,a,a},b,{z,a},{z,a,a},{b,a},z])
+ )
+ ).