summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md371
-rw-r--r--README.md283
-rw-r--r--THANKS3
-rwxr-xr-xbootstrap40
-rw-r--r--priv/shell-completion/bash/rebar340
-rw-r--r--priv/shell-completion/fish/rebar3.fish8
-rw-r--r--priv/shell-completion/zsh/_rebar38
-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.config7
-rw-r--r--rebar.config.sample2
-rw-r--r--rebar.lock6
-rw-r--r--src/rebar3.erl13
-rw-r--r--src/rebar_app_info.erl8
-rw-r--r--src/rebar_app_utils.erl42
-rw-r--r--src/rebar_config.erl38
-rw-r--r--src/rebar_erlc_compiler.erl5
-rw-r--r--src/rebar_file_utils.erl16
-rw-r--r--src/rebar_git_resource.erl4
-rw-r--r--src/rebar_hooks.erl6
-rw-r--r--src/rebar_log.erl27
-rw-r--r--src/rebar_packages.erl79
-rw-r--r--src/rebar_pkg_resource.erl2
-rw-r--r--src/rebar_plugins.erl41
-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.erl54
-rw-r--r--src/rebar_prv_compile.erl44
-rw-r--r--src/rebar_prv_dialyzer.erl23
-rw-r--r--src/rebar_prv_eunit.erl100
-rw-r--r--src/rebar_prv_install_deps.erl13
-rw-r--r--src/rebar_prv_local_install.erl4
-rw-r--r--src/rebar_prv_local_upgrade.erl2
-rw-r--r--src/rebar_prv_lock.erl3
-rw-r--r--src/rebar_prv_shell.erl61
-rw-r--r--src/rebar_prv_unlock.erl9
-rw-r--r--src/rebar_prv_update.erl124
-rw-r--r--src/rebar_prv_upgrade.erl2
-rw-r--r--src/rebar_relx.erl18
-rw-r--r--src/rebar_state.erl31
-rw-r--r--src/rebar_templater.erl25
-rw-r--r--src/rebar_user.erl757
-rw-r--r--src/rebar_utils.erl20
-rw-r--r--test/mock_git_resource.erl11
-rw-r--r--test/rebar_as_SUITE.erl22
-rw-r--r--test/rebar_compile_SUITE.erl102
-rw-r--r--test/rebar_ct_SUITE.erl96
-rw-r--r--test/rebar_deps_SUITE.erl112
-rw-r--r--test/rebar_eunit_SUITE.erl23
-rw-r--r--test/rebar_file_utils_SUITE.erl21
-rw-r--r--test/rebar_hooks_SUITE.erl22
-rw-r--r--test/rebar_lock_SUITE.erl46
-rw-r--r--test/rebar_pkg_SUITE.erl28
-rw-r--r--test/rebar_plugins_SUITE.erl125
-rw-r--r--test/rebar_release_SUITE.erl79
-rw-r--r--test/rebar_test_utils.erl42
66 files changed, 2593 insertions, 550 deletions
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 14e8b61..6e5f102 100644
--- a/README.md
+++ b/README.md
@@ -1,164 +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-4
-====
-
-[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 |
-| state | Display configuration state for debugging purposes |
-| 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)
-
-### 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
@@ -181,3 +167,6 @@ 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/THANKS b/THANKS
index cf59ef1..c5f7522 100644
--- a/THANKS
+++ b/THANKS
@@ -135,4 +135,5 @@ Pierre Fenoll
David Kubecka
Stefan Grundmann
Carlos Eduardo de Paula
-Derek Brown \ No newline at end of file
+Derek Brown
+Heinz N. Gies
diff --git a/bootstrap b/bootstrap
index a26ebff..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} ->
@@ -79,15 +90,20 @@ fetch_and_compile({Name, ErlFirstFiles}, Deps) ->
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 771297c..f436bad 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,7 +52,8 @@ _rebar3()
lopts="--reset --verbose"
elif [[ ${prev} == ct ]] ; then
sopts="-c -v"
- lopts="--dir \
+ lopts=" \
+ --dir \
--suite \
--group \
--case \
@@ -74,8 +77,9 @@ _rebar3()
--multiply_timetraps \
--scale_timetraps \
--create_priv_dir \
- --verbose" \
- --auto_compile
+ --verbose \
+ --auto_compile \
+ "
elif [[ ${prev} == deps ]] ; then
:
elif [[ ${prev} == dialyzer ]] ; then
@@ -88,7 +92,7 @@ _rebar3()
elif [[ ${prev} == escriptize ]] ; then
:
elif [[ ${prev} == eunit ]] ; then
- sopts="-c -e -v"
+ sopts="-c -e -v -d -f -m -s"
lopts="--app --application --cover --dir --error_on_warning --file --module --suite --verbose"
elif [[ ${prev} == help ]] ; then
:
@@ -97,7 +101,8 @@ _rebar3()
lopts="--force"
elif [[ ${prev} == path ]] ; then
sopts="-s"
- lopts="--app \
+ lopts=" \
+ --app \
--base \
--bin \
--ebin \
@@ -105,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 \
@@ -131,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 \
@@ -153,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 \
@@ -179,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 7b63e20..770feea 100644
--- a/priv/shell-completion/fish/rebar3.fish
+++ b/priv/shell-completion/fish/rebar3.fish
@@ -130,11 +130,11 @@ complete -f -c 'rebar3' -n '__fish_rebar3_needs_command' -a eunit -d "Run EUnit
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l app -d "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`"
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l application -d "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`"
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s c -l cover -d "Generate cover data"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l dir -d "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`"
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s d -l dir -d "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`"
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunut' -s e -l error_on_warning -d "Error on invalid test specifications instead of warning"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l file -d "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l module -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`"
-complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l suite -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`"
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s f -l file -d "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`"
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s m -l module -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`"
+complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s s -l suite -d "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`"
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -s v -l verbose -d "Verbose output"
complete -f -c 'rebar3' -n '__fish_rebar3_using_command eunit' -l suite -d "Lists of test suites to run"
diff --git a/priv/shell-completion/zsh/_rebar3 b/priv/shell-completion/zsh/_rebar3
index f0fb351..a3c9c58 100644
--- a/priv/shell-completion/zsh/_rebar3
+++ b/priv/shell-completion/zsh/_rebar3
@@ -89,11 +89,11 @@ _rebar3 () {
'(--app)--app[Comma separated list of application test suites to run]:suites' \
'(--application)--application[Comma separated list of application test suites to run]:applications' \
'(-c --cover)'{-c,--cover}'[Generate cover data]' \
- '(--dir)--dir[Comma separated list of dirs to load tests from]:dirs' \
+ '(-d --dir)'{-d,--dir}'[Comma separated list of dirs to load tests from]:dirs' \
'(-e --error_on_warning)'{-e,--error_on_warning}'[Error on invalid test specifications instead of warning]' \
- '(--file)--file[Comma separated list of files to load tests from]:files' \
- '(--module)--module[Comma separated list of modules to load tests from]:modules' \
- '(--suite)--suite[Comma separated list of modules to load tests from]:modules' \
+ '(-f --file)'{-f,--file}'[Comma separated list of files to load tests from]:files' \
+ '(-m --module)'{-m,--module}'[Comma separated list of modules to load tests from]:modules' \
+ '(-s --suite)'{-s,--suite}'[Comma separated list of modules to load tests from]:modules' \
'(-v --verbose)'{-v,--verbose}'[Verbose output]' \
&& ret=0
;;
diff --git a/priv/templates/app.erl b/priv/templates/app.erl
index 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 276acff..db6bf5f 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,15 +1,15 @@
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 ft=erlang et
-{deps, [{erlware_commons, "0.18.0"},
+{deps, [{erlware_commons, "0.19.0"},
{ssl_verify_hostname, "1.0.5"},
{certifi, "0.3.0"},
{providers, "1.6.0"},
{getopt, "0.8.2"},
{bbmustache, "1.0.4"},
- {relx, "3.9.0"},
+ {relx, "3.17.0"},
{cf, "0.2.1"},
- {cth_readable, "1.1.0"},
+ {cth_readable, "1.2.1"},
{eunit_formatters, "0.3.1"}]}.
{escript_name, rebar3}.
@@ -20,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.config.sample b/rebar.config.sample
index de70998..f57f8dc 100644
--- a/rebar.config.sample
+++ b/rebar.config.sample
@@ -211,7 +211,7 @@
%% apps to auto-boot with `rebar3 shell'; defaults to apps
%% specified in a `relx' tuple, if any.
-{shell_apps, [app1, app2]}
+{shell, [{apps, [app1, app2]}]}.
%% == xref ==
diff --git a/rebar.lock b/rebar.lock
index 8edc3d7..f715a46 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,10 +1,10 @@
[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0},
{<<"certifi">>,{pkg,<<"certifi">>,<<"0.3.0">>},0},
{<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0},
- {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.1.0">>},0},
- {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.18.0">>},0},
+ {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.1">>},0},
+ {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.19.0">>},0},
{<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0},
{<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0},
- {<<"relx">>,{pkg,<<"relx">>,<<"3.9.0">>},0},
+ {<<"relx">>,{pkg,<<"relx">>,<<"3.17.0">>},0},
{<<"ssl_verify_hostname">>,{pkg,<<"ssl_verify_hostname">>,<<"1.0.5">>},0}].
diff --git a/src/rebar3.erl b/src/rebar3.erl
index 90f2845..c1a1ae4 100644
--- a/src/rebar3.erl
+++ b/src/rebar3.erl
@@ -105,6 +105,9 @@ 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;
@@ -123,14 +126,16 @@ run_aux(State, RawArgs) ->
{ok, Providers} = application:get_env(rebar, providers),
%% Providers can modify profiles stored in opts, so set default after initializing providers
State5 = rebar_state:create_logic_providers(Providers, State4),
- State6 = rebar_plugins:project_apps_install(State5),
- State7 = rebar_state:default(State6, rebar_state:opts(State6)),
+ %% Initializing project_plugins which can override default providers
+ State6 = rebar_plugins:project_plugins_install(State5),
+ State7 = rebar_plugins:top_level_install(State6),
+ State8 = rebar_state:default(State7, rebar_state:opts(State7)),
{Task, Args} = parse_args(RawArgs),
- State8 = rebar_state:code_paths(State7, default, code:get_path()),
+ State9 = rebar_state:code_paths(State8, default, code:get_path()),
- rebar_core:init_command(rebar_state:command_args(State8, Args), Task).
+ rebar_core:init_command(rebar_state:command_args(State9, Args), Task).
init_config() ->
%% Initialize logging system
diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl
index 9fee4e0..cf3b82e 100644
--- a/src/rebar_app_info.erl
+++ b/src/rebar_app_info.erl
@@ -165,13 +165,13 @@ update_opts(AppInfo, Opts, Config) ->
deps_from_config(Dir, Config) ->
case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
- [D] ->
+ [] ->
+ [{{deps, default}, proplists:get_value(deps, Config, [])}];
+ D ->
%% We want the top level deps only from the lock file.
%% This ensures deterministic overrides for configs.
Deps = [X || X <- D, element(3, X) =:= 0],
- [{{locks, default}, D}, {{deps, default}, Deps}];
- _ ->
- [{{deps, default}, proplists:get_value(deps, Config, [])}]
+ [{{locks, default}, D}, {{deps, default}, Deps}]
end.
%% @doc discover a complete version of the app info with all fields set.
diff --git a/src/rebar_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_config.erl b/src/rebar_config.erl
index 61301cb..8d7bcf4 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -30,6 +30,7 @@
,consult_app_file/1
,consult_file/1
,consult_lock_file/1
+ ,write_lock_file/2
,verify_config_format/1
,format_error/1
@@ -50,7 +51,40 @@ consult_app_file(File) ->
consult_file_(File).
consult_lock_file(File) ->
- consult_file_(File).
+ Terms = consult_file_(File),
+ case Terms of
+ [] ->
+ [];
+ [Locks] when is_list(Locks) -> % beta lock file
+ Locks;
+ [{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file
+ %% Make sure the warning above is to be shown whenever a version
+ %% newer than the current one is being used, as we can't parse
+ %% all the contents of the lock file properly.
+ ?WARN("Rebar3 detected a lock file from a newer version. "
+ "It will be loaded in compatibility mode, but important "
+ "information may be missing or lost. It is recommended to "
+ "upgrade Rebar3.", []),
+ read_attrs(Vsn, Locks, Attrs)
+ end.
+
+write_lock_file(LockFile, Locks) ->
+ NewLocks = write_attrs(Locks),
+ %% Write locks in the beta format, at least until it's been long
+ %% enough we can start modifying the lock format.
+ file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])).
+
+read_attrs(_Vsn, Locks, _Attrs) ->
+ %% Beta copy does not know how to expand attributes, but
+ %% is ready to support it.
+ Locks.
+
+write_attrs(Locks) ->
+ %% No attribute known that needs to be taken out of the structure,
+ %% just return terms as is.
+ Locks.
+
+
consult_file(File) ->
Terms = consult_file_(File),
@@ -87,7 +121,7 @@ verify_config_format([Term | _]) ->
merge_locks(Config, []) ->
Config;
%% lockfile with entries
-merge_locks(Config, [Locks]) ->
+merge_locks(Config, Locks) ->
ConfigDeps = proplists:get_value(deps, Config, []),
%% We want the top level deps only from the lock file.
%% This ensures deterministic overrides for configs.
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 162ed07..3480cf6 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -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;
diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl
index 8c57d19..0f84520 100644
--- a/src/rebar_file_utils.erl
+++ b/src/rebar_file_utils.erl
@@ -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_hooks.erl b/src/rebar_hooks.erl
index a5ab048..3af17ca 100644
--- a/src/rebar_hooks.erl
+++ b/src/rebar_hooks.erl
@@ -2,6 +2,7 @@
-export([run_all_hooks/5
,run_all_hooks/6
+ ,run_project_and_app_hooks/5
,format_error/1]).
-include("rebar.hrl").
@@ -20,6 +21,11 @@ run_all_hooks(Dir, Type, Command, Providers, State) ->
run_provider_hooks(Dir, Type, Command, Providers, rebar_state:opts(State), State),
run_hooks(Dir, Type, Command, rebar_state:opts(State), State).
+run_project_and_app_hooks(Dir, Type, Command, Providers, State) ->
+ ProjectApps = rebar_state:project_apps(State),
+ [rebar_hooks:run_all_hooks(Dir, Type, Command, Providers, AppInfo, State) || AppInfo <- ProjectApps],
+ run_all_hooks(Dir, Type, Command, Providers, State).
+
run_provider_hooks(Dir, Type, Command, Providers, Opts, State) ->
case rebar_opts:get(Opts, provider_hooks, []) of
[] ->
diff --git a/src/rebar_log.erl b/src/rebar_log.erl
index 06cfa9c..be92199 100644
--- a/src/rebar_log.erl
+++ b/src/rebar_log.erl
@@ -30,6 +30,7 @@
set_level/1,
error_level/0,
default_level/0,
+ intensity/0,
log/3,
is_verbose/1]).
@@ -37,11 +38,31 @@
-define(WARN_LEVEL, 1).
-define(INFO_LEVEL, 2).
-define(DEBUG_LEVEL, 3).
+-define(DFLT_INTENSITY, high).
%% ===================================================================
%% Public API
%% ===================================================================
+%% @doc Returns the color intensity, we first check the application envorinment
+%% if that is not set we check the environment variable REBAR_COLOR.
+intensity() ->
+ case application:get_env(rebar, color_intensity) of
+ undefined ->
+ R = case os:getenv("REBAR_COLOR") of
+ "high" ->
+ high;
+ "low" ->
+ low;
+ _ ->
+ ?DFLT_INTENSITY
+ end,
+ application:set_env(rebar, color_intensity, R),
+ R;
+ {ok, Mode} ->
+ Mode
+ end.
+
init(Caller, Verbosity) ->
Level = case valid_level(Verbosity) of
?ERROR_LEVEL -> error;
@@ -49,12 +70,16 @@ init(Caller, Verbosity) ->
?INFO_LEVEL -> info;
?DEBUG_LEVEL -> debug
end,
- Log = ec_cmd_log:new(Level, Caller),
+ Intensity = intensity(),
+ Log = ec_cmd_log:new(Level, Caller, Intensity),
application:set_env(rebar, log, Log).
set_level(Level) ->
ok = application:set_env(rebar, log_level, Level).
+log(Level = error, Str, Args) ->
+ {ok, LogState} = application:get_env(rebar, log),
+ ec_cmd_log:Level(LogState, lists:flatten(cf:format("~!^~s~n", [Str])), Args);
log(Level, Str, Args) ->
{ok, LogState} = application:get_env(rebar, log),
ec_cmd_log:Level(LogState, Str++"~n", Args).
diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl
index c56009e..d4b8a14 100644
--- a/src/rebar_packages.erl
+++ b/src/rebar_packages.erl
@@ -7,7 +7,9 @@
,registry_dir/1
,package_dir/1
,registry_checksum/2
+ ,find_highest_matching/6
,find_highest_matching/4
+ ,find_all/3
,verify_table/1
,format_error/1]).
@@ -65,22 +67,28 @@ deps(Name, Vsn, State) ->
deps_(Name, Vsn, State)
catch
_:_ ->
- handle_missing_package(Name, Vsn, State)
+ handle_missing_package({Name, Vsn}, State, fun(State1) -> deps_(Name, Vsn, State1) end)
end.
deps_(Name, Vsn, State) ->
?MODULE:verify_table(State),
ets:lookup_element(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, 2).
-handle_missing_package(Name, Vsn, State) ->
- ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]),
+handle_missing_package(Dep, State, Fun) ->
+ case Dep of
+ {Name, Vsn} ->
+ ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]);
+ _ ->
+ ?INFO("Package ~p not found. Fetching registry updates and trying again...", [Dep])
+ end,
+
{ok, State1} = rebar_prv_update:do(State),
try
- deps_(Name, Vsn, State1)
+ Fun(State1)
catch
_:_ ->
%% Even after an update the package is still missing, time to error out
- throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}))
+ throw(?PRV_ERROR({missing_package, Dep}))
end.
registry_dir(State) ->
@@ -139,16 +147,43 @@ registry_checksum({pkg, Name, Vsn}, State) ->
%% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
%% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
find_highest_matching(Dep, Constraint, Table, State) ->
+ find_highest_matching(undefined, undefined, Dep, Constraint, Table, State).
+
+find_highest_matching(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
+ try find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) of
+ none ->
+ handle_missing_package(Dep, State,
+ fun(State1) ->
+ find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
+ end);
+ Result ->
+ Result
+ catch
+ _:_ ->
+ handle_missing_package(Dep, State,
+ fun(State1) ->
+ find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
+ end)
+ end.
+
+find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
+ try find_all(Dep, Table, State) of
+ {ok, [Vsn]} ->
+ handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint);
+ {ok, [HeadVsn | VsnTail]} ->
+ {ok, handle_vsns(Constraint, HeadVsn, VsnTail)}
+ catch
+ error:badarg ->
+ none
+ end.
+
+find_all(Dep, Table, State) ->
?MODULE:verify_table(State),
try ets:lookup_element(Table, Dep, 2) of
- [[HeadVsn | VsnTail]] ->
- {ok, handle_vsns(Constraint, HeadVsn, VsnTail)};
- [[Vsn]] ->
- handle_single_vsn(Dep, Vsn, Constraint);
- [Vsn] ->
- handle_single_vsn(Dep, Vsn, Constraint);
- [HeadVsn | VsnTail] ->
- {ok, handle_vsns(Constraint, HeadVsn, VsnTail)}
+ [Vsns] when is_list(Vsns)->
+ {ok, Vsns};
+ Vsns ->
+ {ok, Vsns}
catch
error:badarg ->
none
@@ -165,18 +200,26 @@ handle_vsns(Constraint, HeadVsn, VsnTail) ->
end
end, HeadVsn, VsnTail).
-handle_single_vsn(Dep, Vsn, Constraint) ->
+handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint) ->
case ec_semver:pes(Vsn, Constraint) of
true ->
{ok, Vsn};
false ->
- ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
- "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]),
+ case {Pkg, PkgVsn} of
+ {undefined, undefined} ->
+ ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
+ "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]);
+ _ ->
+ ?WARN("[~s:~s] Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
+ "Using anyway, but it is not guaranteed to work.", [Pkg, PkgVsn, Dep, Vsn, Constraint])
+ end,
{ok, Vsn}
end.
-format_error({missing_package, Package, Version}) ->
- io_lib:format("Package not found in registry: ~s-~s.", [Package, Version]).
+format_error({missing_package, {Name, Vsn}}) ->
+ io_lib:format("Package not found in registry: ~s-~s.", [ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)]);
+format_error({missing_package, Dep}) ->
+ io_lib:format("Package not found in registry: ~p.", [Dep]).
verify_table(State) ->
ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State).
diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl
index 33687e4..ec7e09d 100644
--- a/src/rebar_pkg_resource.erl
+++ b/src/rebar_pkg_resource.erl
@@ -104,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..68ba6da 100644
--- a/src/rebar_plugins.erl
+++ b/src/rebar_plugins.erl
@@ -3,7 +3,9 @@
-module(rebar_plugins).
--export([project_apps_install/1
+-export([project_plugins_install/1
+ ,top_level_install/1
+ ,project_apps_install/1
,install/2
,handle_plugins/3
,handle_plugins/4]).
@@ -14,11 +16,28 @@
%% Public API
%% ===================================================================
+-spec project_plugins_install(rebar_state:t()) -> rebar_state:t().
+project_plugins_install(State) ->
+ Profiles = rebar_state:current_profiles(State),
+ State1 = rebar_state:allow_provider_overrides(State, true),
+ State2 = lists:foldl(fun(Profile, StateAcc) ->
+ Plugins = rebar_state:get(State, {project_plugins, Profile}, []),
+ handle_plugins(Profile, Plugins, StateAcc)
+ end, State1, Profiles),
+ rebar_state:allow_provider_overrides(State2, false).
+
+-spec top_level_install(rebar_state:t()) -> rebar_state:t().
+top_level_install(State) ->
+ Profiles = rebar_state:current_profiles(State),
+ 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 +53,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 05a1dc6..180061c 100644
--- a/src/rebar_prv_common_test.erl
+++ b/src/rebar_prv_common_test.erl
@@ -52,14 +52,16 @@ do(State, Tests) ->
%% Run ct provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
- rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
+
+ %% Run ct provider pre hooks for all project apps and top level project hooks
+ rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),
case Tests of
{ok, T} ->
case run_tests(State, T) of
ok ->
- %% Run ct provider posthooks
- rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State),
+ %% Run ct provider post hooks for all project apps and top level project hooks
+ rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State),
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
{ok, State};
Error ->
@@ -281,7 +283,8 @@ inject_ct_state(State, [App|Rest], Acc) ->
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)}}
+ NewOpts ->
+ {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
end.
opts(Opts, Key, Default) ->
@@ -340,7 +343,7 @@ test_dirs(State, Apps, Opts) ->
{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));
+ set_compile_dirs(State, Apps, join(Suites, Dir));
{_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"}
end.
@@ -375,6 +378,15 @@ find_suite_dirs(Suites) ->
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};
@@ -384,8 +396,15 @@ maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
maybe_inject_test_dir(State, AppAcc, [], Dir) ->
case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
{ok, []} ->
- ?WARN("Can't have suites in root of project dir, dropping from tests", []),
- {State, AppAcc};
+ %% 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};
@@ -393,6 +412,14 @@ maybe_inject_test_dir(State, AppAcc, [], Dir) ->
{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, []),
@@ -423,15 +450,23 @@ 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(State, Apps, Suite)}|Acc]);
+ 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(State, Apps, Suite) end, Suites)},
+ 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]);
@@ -620,4 +655,3 @@ help(verbose) ->
"Verbose output";
help(_) ->
"".
-
diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl
index 2996aee..effc763 100644
--- a/src/rebar_prv_compile.erl
+++ b/src/rebar_prv_compile.erl
@@ -13,6 +13,8 @@
-include("rebar.hrl").
-define(PROVIDER, compile).
+-define(ERLC_HOOK, erlc_compile).
+-define(APP_HOOK, app_compile).
-define(DEPS, [lock]).
%% ===================================================================
@@ -116,12 +118,17 @@ compile(State, Providers, AppInfo) ->
AppDir = rebar_app_info:dir(AppInfo),
AppInfo1 = rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, AppInfo, State),
- rebar_erlc_compiler:compile(AppInfo1),
- case rebar_otp_app:compile(State, AppInfo1) of
- {ok, AppInfo2} ->
- AppInfo3 = rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, AppInfo2, State),
- has_all_artifacts(AppInfo3),
- AppInfo3;
+ AppInfo2 = rebar_hooks:run_all_hooks(AppDir, pre, ?ERLC_HOOK, Providers, AppInfo1, State),
+ rebar_erlc_compiler:compile(AppInfo2),
+ AppInfo3 = rebar_hooks:run_all_hooks(AppDir, post, ?ERLC_HOOK, Providers, AppInfo2, State),
+
+ AppInfo4 = rebar_hooks:run_all_hooks(AppDir, pre, ?APP_HOOK, Providers, AppInfo3, State),
+ case rebar_otp_app:compile(State, AppInfo4) of
+ {ok, AppInfo5} ->
+ AppInfo6 = rebar_hooks:run_all_hooks(AppDir, post, ?APP_HOOK, Providers, AppInfo5, State),
+ AppInfo7 = rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, AppInfo6, State),
+ has_all_artifacts(AppInfo5),
+ AppInfo7;
Error ->
throw(Error)
end.
@@ -143,7 +150,7 @@ paths_for_apps([App|Rest], Acc) ->
Paths = [filename:join([rebar_app_info:out_dir(App), Dir]) || Dir <- ["ebin"|ExtraDirs]],
FilteredPaths = lists:filter(fun ec_file:is_dir/1, Paths),
paths_for_apps(Rest, Acc ++ FilteredPaths).
-
+
paths_for_extras(State, Apps) ->
F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
%% check that this app hasn't already been dealt with
@@ -217,6 +224,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 +254,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_dialyzer.erl b/src/rebar_prv_dialyzer.erl
index 2e5728a..834eb98 100644
--- a/src/rebar_prv_dialyzer.erl
+++ b/src/rebar_prv_dialyzer.erl
@@ -397,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]),
@@ -412,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),
@@ -475,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 b9ac6b8..c085ee4 100644
--- a/src/rebar_prv_eunit.erl
+++ b/src/rebar_prv_eunit.erl
@@ -56,14 +56,14 @@ do(State, Tests) ->
%% Run eunit provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
- rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
+ rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),
case validate_tests(State, Tests) of
{ok, T} ->
case run_tests(State, T) of
{ok, State1} ->
%% Run eunit provider posthooks
- rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1),
+ rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State1),
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
{ok, State1};
Error ->
@@ -139,7 +139,8 @@ normalize(Key, Value) -> {Key, list_to_atom(Value)}.
cfg_tests(State) ->
case rebar_state:get(State, eunit_tests, []) of
- Tests when is_list(Tests) -> Tests;
+ 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}}})
@@ -152,22 +153,51 @@ select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests};
select_tests(_State, _ProjectApps, Tests, _) -> {ok, 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])
- end.
+ %% 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),
case inject_eunit_state(State, Apps, []) of
@@ -333,10 +363,9 @@ 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) ->
@@ -369,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, [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) ->
+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) ->
@@ -417,10 +457,10 @@ eunit_opts(_State) ->
[{app, undefined, "app", string, help(app)},
{application, undefined, "application", string, help(app)},
{cover, $c, "cover", boolean, help(cover)},
- {dir, undefined, "dir", string, help(dir)},
- {file, undefined, "file", string, help(file)},
- {module, undefined, "module", string, help(module)},
- {suite, undefined, "suite", string, help(module)},
+ {dir, $d, "dir", string, help(dir)},
+ {file, $f, "file", string, help(file)},
+ {module, $m, "module", string, help(module)},
+ {suite, $s, "suite", string, help(module)},
{verbose, $v, "verbose", boolean, help(verbose)}].
help(app) -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`.";
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index 118d799..5e6aa4c 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -35,7 +35,8 @@
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
--export([handle_deps_as_profile/4,
+-export([do_/1,
+ handle_deps_as_profile/4,
profile_dep_dir/2,
find_cycles/1,
cull_compile/2]).
@@ -69,8 +70,11 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
+ ?INFO("Verifying dependencies...", []),
+ do_(State).
+
+do_(State) ->
try
- ?INFO("Verifying dependencies...", []),
Profiles = rebar_state:current_profiles(State),
ProjectApps = rebar_state:project_apps(State),
@@ -251,13 +255,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 +272,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_lock.erl b/src/rebar_prv_lock.erl
index 8578979..cbe8dfe 100644
--- a/src/rebar_prv_lock.erl
+++ b/src/rebar_prv_lock.erl
@@ -35,8 +35,7 @@ do(State) ->
OldLocks = rebar_state:get(State, {locks, default}, []),
Locks = lists:keysort(1, build_locks(State)),
Dir = rebar_state:dir(State),
- file:write_file(filename:join(Dir, ?LOCK_FILE),
- io_lib:format("~p.~n", [Locks])),
+ rebar_config:write_lock_file(filename:join(Dir, ?LOCK_FILE), Locks),
State1 = rebar_state:set(State, {locks, default}, Locks),
OldLockNames = [element(1,L) || L <- OldLocks],
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index c644930..430d1e8 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).
@@ -63,6 +64,8 @@ init(State) ->
"Gives a long name to the node."},
{sname, undefined, "sname", atom,
"Gives a short name to the node."},
+ {setcookie, undefined, "setcookie", atom,
+ "Sets the cookie if the node is distributed."},
{script_file, undefined, "script", string,
"Path to an escript file to run before "
"starting the project apps. Defaults to "
@@ -111,16 +114,44 @@ 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
+ 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
+ %% 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.
@@ -154,6 +185,7 @@ setup_shell() ->
hope_for_best
end.
+
setup_paths(State) ->
%% Add deps to path
code:add_pathsa(rebar_state:code_paths(State, all_deps)),
@@ -228,9 +260,11 @@ setup_name(State) ->
{undefined, undefined} ->
ok;
{Name, undefined} ->
- check_epmd(net_kernel:start([Name, longnames]));
+ check_epmd(net_kernel:start([Name, longnames])),
+ setup_cookie(Opts);
{undefined, SName} ->
- check_epmd(net_kernel:start([SName, shortnames]));
+ check_epmd(net_kernel:start([SName, shortnames])),
+ setup_cookie(Opts);
{_, _} ->
?ABORT("Cannot have both short and long node names defined", [])
end.
@@ -241,6 +275,14 @@ check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) ->
check_epmd(_) ->
ok.
+setup_cookie(Opts) ->
+ case {node(), proplists:get_value(setcookie, Opts, nocookie)} of
+ {'nonode@nohost', _} -> nocookie;
+ {_, nocookie} -> nocookie;
+ {Node, Name} -> erlang:set_cookie(Node, Name)
+ end.
+
+
find_apps_to_boot(State) ->
%% Try the shell_apps option
case first_value([fun find_apps_option/1,
@@ -295,9 +337,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.
diff --git a/src/rebar_prv_unlock.erl b/src/rebar_prv_unlock.erl
index b049c92..7ff0d89 100644
--- a/src/rebar_prv_unlock.erl
+++ b/src/rebar_prv_unlock.erl
@@ -46,15 +46,14 @@ do(State) ->
{ok, State};
{error, Reason} ->
?PRV_ERROR({file,Reason});
- {ok, [Locks]} ->
+ {ok, _} ->
+ Locks = rebar_config:consult_lock_file(LockFile),
case handle_unlocks(State, Locks, LockFile) of
ok ->
{ok, State};
{error, Reason} ->
?PRV_ERROR({file,Reason})
- end;
- {ok, _Other} ->
- ?PRV_ERROR(unknown_lock_format)
+ end
end.
-spec format_error(any()) -> iolist().
@@ -74,7 +73,7 @@ handle_unlocks(State, Locks, LockFile) ->
_ when Names =:= [] -> % implicitly all locks
file:delete(LockFile);
NewLocks ->
- file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks]))
+ rebar_config:write_lock_file(LockFile, NewLocks)
end.
parse_names(Bin) ->
diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl
index 1cdf6af..5e1e253 100644
--- a/src/rebar_prv_update.erl
+++ b/src/rebar_prv_update.erl
@@ -11,6 +11,10 @@
-export([hex_to_index/1]).
+-ifdef(TEST).
+-export([cmp_/6, cmpl_/6, valid_vsn/1]).
+-endif.
+
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -48,7 +52,7 @@ do(State) ->
case rebar_utils:url_append_path(CDN, ?REMOTE_REGISTRY_FILE) of
{ok, Url} ->
?DEBUG("Fetching registry from ~p", [Url]),
- case httpc:request(get, {Url, []},
+ case httpc:request(get, {Url, [{"User-Agent", rebar_utils:user_agent()}]},
[], [{stream, TmpFile}, {sync, true}],
rebar) of
{ok, saved_to_file} ->
@@ -99,7 +103,7 @@ hex_to_index(State) ->
ets:foldl(fun({{Pkg, PkgVsn}, [Deps, Checksum, BuildTools | _]}, _) when is_list(BuildTools) ->
case lists:any(fun is_supported/1, BuildTools) of
true ->
- DepsList = update_deps_list(Deps, Registry, State),
+ DepsList = update_deps_list(Pkg, PkgVsn, Deps, Registry, State),
ets:insert(?PACKAGE_TABLE, {{Pkg, PkgVsn}, DepsList, Checksum});
false ->
true
@@ -137,20 +141,114 @@ hex_to_index(State) ->
fail
end.
-update_deps_list(Deps, HexRegistry, State) ->
+update_deps_list(Pkg, PkgVsn, Deps, HexRegistry, State) ->
lists:foldl(fun([Dep, DepVsn, false, _AppName | _], DepsListAcc) ->
- case DepVsn of
- <<"~> ", Vsn/binary>> ->
- case rebar_packages:find_highest_matching(Dep, Vsn, HexRegistry, State) of
- {ok, HighestDepVsn} ->
- [{Dep, HighestDepVsn} | DepsListAcc];
- none ->
- ?WARN("Missing registry entry for package ~s. Try to fix with `rebar3 update`", [Dep]),
- DepsListAcc
- end;
- Vsn ->
+ Dep1 = {Pkg, PkgVsn, Dep},
+ case {valid_vsn(DepVsn), DepVsn} of
+ %% Those are all not perfectly implemented!
+ %% and doubled since spaces seem not to be
+ %% enforced
+ {false, Vsn} ->
+ ?WARN("[~s:~s], Bad dependency version for ~s: ~s.",
+ [Pkg, PkgVsn, Dep, Vsn]),
+ DepsListAcc;
+ {_, <<"~>", Vsn/binary>>} ->
+ highest_matching(Dep1, rm_ws(Vsn), HexRegistry,
+ State, DepsListAcc);
+ {_, <<">=", Vsn/binary>>} ->
+ cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
+ DepsListAcc, fun ec_semver:gte/2);
+ {_, <<">", Vsn/binary>>} ->
+ cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
+ DepsListAcc, fun ec_semver:gt/2);
+ {_, <<"<=", Vsn/binary>>} ->
+ cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
+ DepsListAcc, fun ec_semver:lte/2);
+ {_, <<"<", Vsn/binary>>} ->
+ cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
+ DepsListAcc, fun ec_semver:lt/2);
+ {_, <<"==", Vsn/binary>>} ->
+ [{Dep, Vsn} | DepsListAcc];
+ {_, Vsn} ->
[{Dep, Vsn} | DepsListAcc]
end;
([_Dep, _DepVsn, true, _AppName | _], DepsListAcc) ->
DepsListAcc
end, [], Deps).
+
+rm_ws(<<" ", R/binary>>) ->
+ rm_ws(R);
+rm_ws(R) ->
+ R.
+
+valid_vsn(Vsn) ->
+ %% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js
+ SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?"
+ "(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?",
+ SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$",
+ re:run(Vsn, SupportedVersions) =/= nomatch.
+
+highest_matching({Pkg, PkgVsn, Dep}, Vsn, HexRegistry, State, DepsListAcc) ->
+ case rebar_packages:find_highest_matching(Pkg, PkgVsn, Dep, Vsn, HexRegistry, State) of
+ {ok, HighestDepVsn} ->
+ [{Dep, HighestDepVsn} | DepsListAcc];
+ none ->
+ ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
+ [Pkg, PkgVsn, Dep]),
+ DepsListAcc
+ end.
+
+cmp({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
+ {ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
+ cmp_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
+
+
+cmp_(undefined, _MinVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) ->
+ ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
+ [Pkg, PkgVsn, Dep]),
+ DepsListAcc;
+cmp_(HighestDepVsn, _MinVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) ->
+ [{Dep, HighestDepVsn} | DepsListAcc];
+
+cmp_(BestMatch, MinVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
+ case CmpFun(Vsn, MinVsn) of
+ true ->
+ cmp_(Vsn, Vsn, R, DepsListAcc, Dep, CmpFun);
+ false ->
+ cmp_(BestMatch, MinVsn, R, DepsListAcc, Dep, CmpFun)
+ end.
+
+%% We need to treat this differently since we want a version that is LOWER but
+%% the higest possible one.
+cmpl({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
+ {ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
+ cmpl_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
+
+cmpl_(undefined, _MaxVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) ->
+ ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
+ [Pkg, PkgVsn, Dep]),
+ DepsListAcc;
+
+cmpl_(HighestDepVsn, _MaxVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) ->
+ [{Dep, HighestDepVsn} | DepsListAcc];
+
+cmpl_(undefined, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
+ case CmpFun(Vsn, MaxVsn) of
+ true ->
+ cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
+ false ->
+ cmpl_(undefined, MaxVsn, R, DepsListAcc, Dep, CmpFun)
+ end;
+
+cmpl_(BestMatch, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
+ case CmpFun(Vsn, MaxVsn) of
+ true ->
+ case ec_semver:gte(Vsn, BestMatch) of
+ true ->
+ cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
+ false ->
+ cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
+ end;
+ false ->
+ cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
+ end.
diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl
index a2864ab..c5c43e4 100644
--- a/src/rebar_prv_upgrade.erl
+++ b/src/rebar_prv_upgrade.erl
@@ -61,7 +61,7 @@ do(State) ->
State4 = rebar_state:set(State3, upgrade, true),
UpdatedLocks = [L || L <- rebar_state:lock(State4),
lists:keymember(rebar_app_info:name(L), 1, Locks0)],
- Res = rebar_prv_install_deps:do(rebar_state:lock(State4, UpdatedLocks)),
+ Res = rebar_prv_install_deps:do_(rebar_state:lock(State4, UpdatedLocks)),
case Res of
{ok, State5} ->
rebar_utils:info_useless(
diff --git a/src/rebar_relx.erl b/src/rebar_relx.erl
index cb8fac5..125da47 100644
--- a/src/rebar_relx.erl
+++ b/src/rebar_relx.erl
@@ -14,6 +14,8 @@
-spec do(atom(), string(), atom(), rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(Module, Command, Provider, State) ->
+ %% We set the color mode for relx as a application env
+ application:set_env(relx, color_intensity, rebar_log:intensity()),
Options = rebar_state:command_args(State),
DepsDir = rebar_dir:deps_dir(State),
ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS),
@@ -23,18 +25,19 @@ do(Module, Command, Provider, State) ->
AllOptions = string:join([Command | Options], " "),
Cwd = rebar_state:dir(State),
Providers = rebar_state:providers(State),
- rebar_hooks:run_all_hooks(Cwd, pre, Provider, Providers, State),
+ rebar_hooks:run_project_and_app_hooks(Cwd, pre, Provider, Providers, State),
try
case rebar_state:get(State, relx, []) of
[] ->
relx:main([{lib_dirs, LibDirs}
,{caller, api} | output_dir(OutputDir, Options)], AllOptions);
Config ->
+ Config1 = merge_overlays(Config),
relx:main([{lib_dirs, LibDirs}
- ,{config, Config}
+ ,{config, Config1}
,{caller, api} | output_dir(OutputDir, Options)], AllOptions)
end,
- rebar_hooks:run_all_hooks(Cwd, post, Provider, Providers, State),
+ rebar_hooks:run_project_and_app_hooks(Cwd, post, Provider, Providers, State),
{ok, State}
catch
throw:T ->
@@ -49,3 +52,12 @@ format_error(Reason) ->
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 9c293f5..a613a00 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -39,7 +39,9 @@
to_list/1,
resources/1, resources/2, add_resource/2,
- providers/1, providers/2, add_provider/2]).
+ providers/1, providers/2, add_provider/2,
+ allow_provider_overrides/1, allow_provider_overrides/2
+ ]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -64,7 +66,8 @@
all_deps = [] :: [rebar_app_info:t()],
resources = [],
- providers = []}).
+ providers = [],
+ allow_provider_overrides = false :: boolean()}).
-export_type([t/0]).
@@ -104,7 +107,8 @@ new(ParentState, Config, Dir) ->
new(ParentState, Config, Deps, Dir) ->
Opts = ParentState#state_t.opts,
Plugins = proplists:get_value(plugins, Config, []),
- Terms = Deps++[{{plugins, default}, Plugins} | Config],
+ ProjectPlugins = proplists:get_value(project_plugins, Config, []),
+ Terms = Deps++[{{project_plugins, default}, ProjectPlugins}, {{plugins, default}, Plugins} | Config],
true = rebar_config:verify_config_format(Terms),
LocalOpts = dict:from_list(Terms),
@@ -116,13 +120,13 @@ new(ParentState, Config, Deps, Dir) ->
deps_from_config(Dir, Config) ->
case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
- [D] ->
+ [] ->
+ [{{deps, default}, proplists:get_value(deps, Config, [])}];
+ D ->
%% We want the top level deps only from the lock file.
%% This ensures deterministic overrides for configs.
Deps = [X || X <- D, element(3, X) =:= 0],
- [{{locks, default}, D}, {{deps, default}, Deps}];
- _ ->
- [{{deps, default}, proplists:get_value(deps, Config, [])}]
+ [{{locks, default}, D}, {{deps, default}, Deps}]
end.
base_state() ->
@@ -137,7 +141,8 @@ base_state() ->
base_opts(Config) ->
Deps = proplists:get_value(deps, Config, []),
Plugins = proplists:get_value(plugins, Config, []),
- Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins} | Config],
+ ProjectPlugins = proplists:get_value(project_plugins, Config, []),
+ Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins}, {{project_plugins, default}, ProjectPlugins} | Config],
true = rebar_config:verify_config_format(Terms),
dict:from_list(Terms).
@@ -369,8 +374,16 @@ providers(#state_t{providers=Providers}) ->
providers(State, NewProviders) ->
State#state_t{providers=NewProviders}.
+allow_provider_overrides(#state_t{allow_provider_overrides=Allow}) ->
+ Allow.
+
+allow_provider_overrides(State, Allow) ->
+ State#state_t{allow_provider_overrides=Allow}.
+
-spec add_provider(t(), providers:t()) -> t().
-add_provider(State=#state_t{providers=Providers}, Provider) ->
+add_provider(State=#state_t{providers=Providers, allow_provider_overrides=true}, Provider) ->
+ State#state_t{providers=[Provider | Providers]};
+add_provider(State=#state_t{providers=Providers, allow_provider_overrides=false}, Provider) ->
Name = providers:impl(Provider),
Namespace = providers:namespace(Provider),
Module = providers:module(Provider),
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index f687637..2f33bfc 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -159,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]),
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 746c57a..56a3940 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -68,7 +68,8 @@
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]).
@@ -407,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
%% ====================================================================
@@ -828,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/mock_git_resource.erl b/test/mock_git_resource.erl
index 0f4aff6..e922af3 100644
--- a/test/mock_git_resource.erl
+++ b/test/mock_git_resource.erl
@@ -1,6 +1,6 @@
%%% Mock a git resource and create an app magically for each URL submitted.
-module(mock_git_resource).
--export([mock/0, mock/1, unmock/0]).
+-export([mock/0, mock/1, mock/2, unmock/0]).
-define(MOD, rebar_git_resource).
%%%%%%%%%%%%%%%%%
@@ -24,11 +24,14 @@ mock() -> mock([]).
| {pkg, App, term()},
Vsn :: string().
mock(Opts) ->
+ mock(Opts, create_app).
+
+mock(Opts, CreateType) ->
meck:new(?MOD, [no_link]),
mock_lock(Opts),
mock_update(Opts),
mock_vsn(Opts),
- mock_download(Opts),
+ mock_download(Opts, CreateType),
ok.
unmock() ->
@@ -98,7 +101,7 @@ mock_vsn(Opts) ->
%% `{deps, [{"app1", [{app2, ".*", {git, ...}}]}]}' -- basically
%% the `deps' option takes a key/value list of terms to output directly
%% into a `rebar.config' file to describe dependencies.
-mock_download(Opts) ->
+mock_download(Opts, CreateType) ->
Deps = proplists:get_value(deps, Opts, []),
Config = proplists:get_value(config, Opts, []),
Default = proplists:get_value(default_vsn, Opts, "0.0.0"),
@@ -110,7 +113,7 @@ mock_download(Opts) ->
{git, Url, {_, Vsn}} = normalize_git(Git, Overrides, Default),
App = app(Url),
AppDeps = proplists:get_value({App,Vsn}, Deps, []),
- rebar_test_utils:create_app(
+ rebar_test_utils:CreateType(
Dir, App, Vsn,
[kernel, stdlib] ++ [element(1,D) || D <- AppDeps]
),
diff --git a/test/rebar_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 ef9cf97..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(),
@@ -1173,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_ct_SUITE.erl b/test/rebar_ct_SUITE.erl
index 267a9ee..94ab690 100644
--- a/test/rebar_ct_SUITE.erl
+++ b/test/rebar_ct_SUITE.erl
@@ -17,6 +17,8 @@
multi_suite/1,
all_suite/1,
single_dir_and_single_suite/1,
+ suite_at_root/1,
+ suite_at_app_root/1,
data_dir_correct/1,
cmd_label/1,
cmd_config/1,
@@ -72,7 +74,9 @@ groups() -> [{basic_app, [], [basic_app_default_dirs,
single_unmanaged_suite,
multi_suite,
all_suite,
- single_dir_and_single_suite]},
+ single_dir_and_single_suite,
+ suite_at_root,
+ suite_at_app_root]},
{data_dirs, [], [data_dir_correct]},
{ct_opts, [], [cmd_label,
cmd_config,
@@ -177,6 +181,22 @@ init_per_group(dirs_and_suites, Config) ->
ok = filelib:ensure_dir(Suite3),
ok = file:write_file(Suite3, test_suite("extras")),
+ 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];
@@ -603,10 +623,84 @@ single_dir_and_single_suite(Config) ->
Suite = proplists:get_value(suite, Opts),
["extra_SUITE"] = Suite.
+suite_at_root(Config) ->
+ AppDir = ?config(apps, 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).
+
+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),
+
+ 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, "apps", Name2, "app_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", "lib", Name2, "app_root_SUITE"]),
+ [Expected] = Suite,
+
+ TestHrl = filename:join([AppDir, "_build", "test", "lib", Name2, "app_root_SUITE.hrl"]),
+ true = filelib:is_file(TestHrl),
+
+ 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) ->
diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl
index fcc46c3..c95854a 100644
--- a/test/rebar_deps_SUITE.erl
+++ b/test/rebar_deps_SUITE.erl
@@ -3,7 +3,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, {group, git}, {group, pkg}].
+all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, semver_matching_lt, semver_matching_lte, semver_matching_gt, valid_version, {group, git}, {group, pkg}].
groups() ->
[{all, [], [flat, pick_highest_left, pick_highest_right,
@@ -29,6 +29,14 @@ init_per_group(_, Config) ->
end_per_group(_, Config) ->
Config.
+init_per_testcase(valid_version, Config) ->
+ rebar_test_utils:init_rebar_state(Config);
+init_per_testcase(semver_matching_lt, Config) ->
+ rebar_test_utils:init_rebar_state(Config);
+init_per_testcase(semver_matching_lte, Config) ->
+ rebar_test_utils:init_rebar_state(Config);
+init_per_testcase(semver_matching_gt, Config) ->
+ rebar_test_utils:init_rebar_state(Config);
init_per_testcase(newly_added_after_empty_lock, Config) ->
rebar_test_utils:init_rebar_state(Config);
init_per_testcase(newly_added_dep, Config) ->
@@ -49,14 +57,14 @@ init_per_testcase(http_proxy_settings, Config) ->
%% Insert proxy variables into config
rebar_test_utils:create_config(GlobalConfigDir,
[{http_proxy, "http://localhost:1234"}
- ]),
+ ]),
rebar_test_utils:init_rebar_state(Config);
init_per_testcase(https_proxy_settings, Config) ->
SupportsHttpsProxy = case erlang:system_info(otp_release) of
- "R16"++_ -> true;
- "R"++_ -> false;
- _ -> true % 17 and up don't have a "R" in the version
- end,
+ "R16"++_ -> true;
+ "R"++_ -> false;
+ _ -> true % 17 and up don't have a "R" in the version
+ end,
if not SupportsHttpsProxy ->
{skip, https_proxy_unsupported_before_R16};
SupportsHttpsProxy ->
@@ -73,20 +81,20 @@ init_per_testcase(https_proxy_settings, Config) ->
%% Insert proxy variables into config
rebar_test_utils:create_config(GlobalConfigDir,
[{https_proxy, "http://localhost:1234"}
- ]),
+ ]),
rebar_test_utils:init_rebar_state(Config)
end;
init_per_testcase(Case, Config) ->
{Deps, Warnings, Expect} = deps(Case),
Expected = case Expect of
- {ok, List} -> {ok, format_expected_deps(List)};
- {error, Reason} -> {error, Reason}
- end,
+ {ok, List} -> {ok, format_expected_deps(List)};
+ {error, Reason} -> {error, Reason}
+ end,
DepsType = ?config(deps_type, Config),
mock_warnings(),
[{expect, Expected},
{warnings, Warnings}
- | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))].
+ | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))].
end_per_testcase(https_proxy_settings, Config) ->
meck:unload(rebar_dir),
@@ -100,8 +108,8 @@ end_per_testcase(_, Config) ->
format_expected_deps(Deps) ->
[case Dep of
- {N,V} -> {dep, N, V};
- N -> {dep, N}
+ {N,V} -> {dep, N, V};
+ N -> {dep, N}
end || Dep <- Deps].
%% format:
@@ -208,7 +216,7 @@ sub_app_deps(Config) ->
SubAppsDir = filename:join([AppDir, "apps", Name]),
SubDeps = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
- ,{"b", "2.0.0", []}])),
+ ,{"b", "2.0.0", []}])),
rebar_test_utils:create_app(SubAppsDir, Name, Vsn, [kernel, stdlib]),
rebar_test_utils:create_config(SubAppsDir, [{deps, SubDeps}]),
@@ -242,12 +250,12 @@ newly_added_dep(Config) ->
%% Add a and c to top level
TopDeps2 = rebar_test_utils:top_level_deps(rebar_test_utils:expand_deps(git, [{"a", "1.0.0", []}
- ,{"c", "2.0.0", []}
- ,{"b", "1.0.0", []}])),
+ ,{"c", "2.0.0", []}
+ ,{"b", "1.0.0", []}])),
{ok, RebarConfig2} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps2}])),
LockFile = filename:join(AppDir, "rebar.lock"),
RebarConfig3 = rebar_config:merge_locks(RebarConfig2,
- rebar_config:consult_lock_file(LockFile)),
+ rebar_config:consult_lock_file(LockFile)),
%% a should now be installed and c should not change
rebar_test_utils:run_and_check(
@@ -277,7 +285,7 @@ newly_added_after_empty_lock(Config) ->
{ok, RebarConfig2} = file:consult(rebar_test_utils:create_config(AppDir, [{deps, TopDeps2}])),
LockFile = filename:join(AppDir, "rebar.lock"),
RebarConfig3 = rebar_config:merge_locks(RebarConfig2,
- rebar_config:consult_lock_file(LockFile)),
+ rebar_config:consult_lock_file(LockFile)),
%% a should now be installed and c should not change
rebar_test_utils:run_and_check(
@@ -304,6 +312,74 @@ https_proxy_settings(_Config) ->
httpc:get_option(https_proxy, rebar)).
+semver_matching_lt(_Config) ->
+ Dep = <<"test">>,
+ Dep1 = {Dep, <<"1.0.0">>, Dep},
+ MaxVsn = <<"0.2.0">>,
+ Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
+ ?assertEqual([{Dep, <<"0.1.9">>}],
+ rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1,
+ fun ec_semver:lt/2)).
+
+semver_matching_lte(_Config) ->
+ Dep = <<"test">>,
+ Dep1 = {Dep, <<"1.0.0">>, Dep},
+ MaxVsn = <<"0.2.0">>,
+ Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
+ ?assertEqual([{Dep, <<"0.2.0">>}],
+ rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1,
+ fun ec_semver:lte/2)).
+
+semver_matching_gt(_Config) ->
+ Dep = <<"test">>,
+ Dep1 = {Dep, <<"1.0.0">>, Dep},
+ MaxVsn = <<"0.2.0">>,
+ Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
+ ?assertEqual([{Dep, <<"0.2.1">>}],
+ rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1,
+ fun ec_semver:gt/2)).
+semver_matching_gte(_Config) ->
+ Dep = <<"test">>,
+ Dep1 = {Dep, <<"1.0.0">>, Dep},
+ MaxVsn = <<"0.2.0">>,
+ Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>],
+ ?assertEqual([{Dep, <<"0.2.0">>}],
+ rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1,
+ fun ec_semver:gt/2)).
+
+valid_version(_Config) ->
+ ?assert(rebar_prv_update:valid_vsn(<<"0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<=0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<=0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">=0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">=0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"==0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"==0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"~>0.1">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"~>0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)),
+ ?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)),
+ ?assertNot(rebar_prv_update:valid_vsn(<<"> 0.1.0 and < 0.2.0">>)),
+ ok.
+
+
run(Config) ->
{ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
rebar_test_utils:run_and_check(
diff --git a/test/rebar_eunit_SUITE.erl b/test/rebar_eunit_SUITE.erl
index 1b11c5a..cb2c911 100644
--- a/test/rebar_eunit_SUITE.erl
+++ b/test/rebar_eunit_SUITE.erl
@@ -42,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) ->
@@ -159,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).
@@ -211,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).
@@ -546,4 +547,4 @@ misspecified_eunit_first_files(Config) ->
{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. \ No newline at end of file
+ {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_hooks_SUITE.erl b/test/rebar_hooks_SUITE.erl
index 188fb34..b121dd5 100644
--- a/test/rebar_hooks_SUITE.erl
+++ b/test/rebar_hooks_SUITE.erl
@@ -10,6 +10,7 @@
escriptize_artifacts/1,
run_hooks_once/1,
run_hooks_for_plugins/1,
+ eunit_app_hooks/1,
deps_hook_namespace/1]).
-include_lib("common_test/include/ct.hrl").
@@ -33,7 +34,7 @@ end_per_testcase(_, _Config) ->
all() ->
[build_and_clean_app, run_hooks_once, escriptize_artifacts,
- run_hooks_for_plugins, deps_hook_namespace].
+ run_hooks_for_plugins, deps_hook_namespace, eunit_app_hooks].
%% Test post provider hook cleans compiled project app, leaving it invalid
build_and_clean_app(Config) ->
@@ -119,6 +120,25 @@ deps_hook_namespace(Config) ->
{ok, [{dep, "some_dep"}]}
).
+%% Checks that a hook that is defined on an app (not a top level hook of a project with subapps) is run
+eunit_app_hooks(Config) ->
+ AppDir = ?config(apps, Config),
+ Name = rebar_test_utils:create_random_name("app1_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ RConfFile =
+ rebar_test_utils:create_config(AppDir,
+ [
+ {escript_name, list_to_atom(Name)}
+ ,{provider_hooks, [{post, [{eunit, escriptize}]}]}
+ ]),
+ {ok, RConf} = file:consult(RConfFile),
+
+ rebar_test_utils:run_and_check(Config, RConf,
+ ["eunit"], {ok, [{app, Name, valid}
+ ,{file, filename:join([AppDir, "_build/test/bin", Name])}]}).
+
run_hooks_for_plugins(Config) ->
AppDir = ?config(apps, Config),
diff --git a/test/rebar_lock_SUITE.erl b/test/rebar_lock_SUITE.erl
new file mode 100644
index 0000000..00875f7
--- /dev/null
+++ b/test/rebar_lock_SUITE.erl
@@ -0,0 +1,46 @@
+%%% Most locking tests are implicit in other test suites handling
+%%% dependencies.
+%%% This suite is to test the compatibility layers between various
+%%% versions of lockfiles.
+-module(rebar_lock_SUITE).
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+all() -> [current_version, future_versions_no_attrs, future_versions_attrs].
+
+current_version(Config) ->
+ %% Current version just dumps the locks as is on disk.
+ LockFile = filename:join(?config(priv_dir, Config), "current_version"),
+ Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2},
+ {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0},
+ {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1},
+ {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}],
+ file:write_file(LockFile, io_lib:format("~p.~n", [Locks])),
+ ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)).
+
+future_versions_no_attrs(Config) ->
+ %% Future versions will keep the same core attribute in there, but
+ %% will do so under a new format bundled with a version and potentially
+ %% some trailing attributes
+ LockFile = filename:join(?config(priv_dir, Config), "future_versions"),
+ Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2},
+ {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0},
+ {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1},
+ {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}],
+ LockData = {"3.5.2", Locks},
+ file:write_file(LockFile, io_lib:format("~p.~n", [LockData])),
+ ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)).
+
+future_versions_attrs(Config) ->
+ %% Future versions will keep the same core attribute in there, but
+ %% will do so under a new format bundled with a version and potentially
+ %% some trailing attributes
+ LockFile = filename:join(?config(priv_dir, Config), "future_versions"),
+ Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2},
+ {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0},
+ {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1},
+ {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}],
+ LockData = {"3.5.2", Locks},
+ file:write_file(LockFile, io_lib:format("~p.~na.~n{b,c}.~n[d,e,f].~n", [LockData])),
+ ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)).
diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl
index 37e3a5e..6a75f32 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),
@@ -86,7 +87,12 @@ init_per_testcase(bad_disconnect=Name, Config0) ->
meck:unload(httpc),
meck:new(httpc, [passthrough, unsticky]),
meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end),
- 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),
@@ -172,6 +178,19 @@ pkgs_provider(Config) ->
{ok, []}
).
+find_highest_matching(_Config) ->
+ State = rebar_state:new(),
+ {ok, Vsn} = rebar_packages:find_highest_matching(
+ <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0.0">>, package_index, State),
+ ?assertEqual(<<"1.0.1">>, Vsn),
+ {ok, Vsn1} = rebar_packages:find_highest_matching(
+ <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0">>, package_index, State),
+ ?assertEqual(<<"1.1.1">>, Vsn1),
+ {ok, Vsn2} = rebar_packages:find_highest_matching(
+ <<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"2.0">>, package_index, State),
+ ?assertEqual(<<"2.0.0">>, Vsn2).
+
+
%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
@@ -182,10 +201,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"]),
diff --git a/test/rebar_plugins_SUITE.erl b/test/rebar_plugins_SUITE.erl
index 3df3c0e..355e156 100644
--- a/test/rebar_plugins_SUITE.erl
+++ b/test/rebar_plugins_SUITE.erl
@@ -10,7 +10,10 @@
compile_global_plugins/1,
complex_plugins/1,
list/1,
- upgrade/1]).
+ upgrade/1,
+ sub_app_plugins/1,
+ sub_app_plugin_overrides/1,
+ project_plugins/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -32,7 +35,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, project_plugins].
%% Tests that compiling a project installs and compiles the plugins of deps
compile_plugins(Config) ->
@@ -208,3 +211,121 @@ 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}]}
+ ).
+
+%% Check that project plugins are first in providers even if they override defaults but that
+%% normal plugins do not
+project_plugins(Config) ->
+ AppDir = ?config(apps, Config),
+
+ Name = rebar_test_utils:create_random_name("app1_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+ DepName = rebar_test_utils:create_random_name("dep1_"),
+ PluginName = "compile",
+ PluginName2 = "release",
+
+ Plugins = rebar_test_utils:expand_deps(git, [{PluginName, Vsn, []}, {PluginName2, Vsn, []}]),
+ {SrcDeps, _} = rebar_test_utils:flat_deps(Plugins),
+ mock_git_resource:mock([{deps, SrcDeps}], create_plugin),
+
+ mock_pkg_resource:mock([{pkgdeps, [{{list_to_binary(DepName), list_to_binary(Vsn)}, []}]},
+ {config, [{plugins, [
+ {list_to_atom(PluginName),
+ {git, "http://site.com/user/"++PluginName++".git",
+ {tag, Vsn}}}]}]}]),
+
+ RConfFile =
+ rebar_test_utils:create_config(AppDir,
+ [{deps, [
+ list_to_atom(DepName)
+ ]},
+ {project_plugins, [
+ {list_to_atom(PluginName2),
+ {git, "http://site.com/user/"++PluginName2++".git",
+ {tag, Vsn}}}]}]),
+ {ok, RConf} = file:consult(RConfFile),
+
+ %% Build with deps.
+ {ok, State} = rebar_test_utils:run_and_check(
+ Config, RConf, ["compile"],
+ {ok, [{app, Name}, {plugin, PluginName}, {plugin, PluginName2}, {dep, DepName}]}
+ ),
+
+ %% Should have 2 release providers but only 1 compile provider
+ Release = [P || P <- rebar_state:providers(State), providers:impl(P) =:= release, providers:namespace(P) =:= default],
+ Compile = [P || P <- rebar_state:providers(State), providers:impl(P) =:= compile, providers:namespace(P) =:= default],
+
+ ?assertEqual(length(Release), 2),
+ ?assertEqual(length(Compile), 1).
diff --git a/test/rebar_release_SUITE.erl b/test/rebar_release_SUITE.erl
index e0fa5a0..1125a7e 100644
--- a/test/rebar_release_SUITE.erl
+++ b/test/rebar_release_SUITE.erl
@@ -10,7 +10,8 @@ all() -> [release,
profile_ordering_sys_config_extend,
profile_ordering_sys_config_extend_3_tuple_merge,
extend_release,
- user_output_dir].
+ user_output_dir, profile_overlays,
+ overlay_vars].
init_per_testcase(Case, Config0) ->
Config = rebar_test_utils:init_rebar_state(Config0),
@@ -193,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..23b0178 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_plugin/4, create_eunit_app/4, create_empty_app/4,
+ create_config/2, create_config/3, package_app/3]).
-export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
%%%%%%%%%%%%%%
@@ -82,6 +82,16 @@ create_app(AppDir, Name, Vsn, Deps) ->
write_app_src_file(AppDir, Name, Vsn, Deps),
rebar_app_info:new(Name, Vsn, AppDir, Deps).
+%% @doc Creates a dummy plugin including:
+%% - src/<file>.erl
+%% - src/<file>.app.src
+%% And returns a `rebar_app_info' object.
+create_plugin(AppDir, Name, Vsn, Deps) ->
+ write_plugin_file(AppDir, Name ++ ".erl"),
+ write_src_file(AppDir, "not_a_real_src_" ++ Name ++ ".erl"),
+ write_app_src_file(AppDir, Name, Vsn, Deps),
+ rebar_app_info:new(Name, Vsn, AppDir, Deps).
+
%% @doc Creates a dummy application including:
%% - src/<file>.erl
%% - src/<file>.app.src
@@ -104,11 +114,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) ->
@@ -362,6 +375,11 @@ check_results(AppDir, Expected, ProfileRun) ->
?assert(filelib:is_dir(Dirname))
end, Expected).
+write_plugin_file(Dir, Name) ->
+ Erl = filename:join([Dir, "src", Name]),
+ ok = filelib:ensure_dir(Erl),
+ ok = ec_file:write(Erl, plugin_src_file(Name)).
+
write_src_file(Dir, Name) ->
Erl = filename:join([Dir, "src", Name]),
ok = filelib:ensure_dir(Erl),
@@ -392,6 +410,18 @@ erl_src_file(Name) ->
"-export([main/0]).\n"
"main() -> ok.\n", [filename:basename(Name, ".erl")]).
+plugin_src_file(Name) ->
+ io_lib:format("-module('~s').\n"
+ "-export([init/1]).\n"
+ "init(State) -> \n"
+ "Provider = providers:create([\n"
+ "{name, '~s'},\n"
+ "{module, '~s'}\n"
+ "]),\n"
+ "{ok, rebar_state:add_provider(State, Provider)}.\n", [filename:basename(Name, ".erl"),
+ filename:basename(Name, ".erl"),
+ filename:basename(Name, ".erl")]).
+
erl_eunitized_src_file(Name) ->
io_lib:format("-module('~s').\n"
"-export([main/0]).\n"