diff options
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. @@ -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). + @@ -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 @@ -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 == @@ -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" |