323 lines
12 KiB
Markdown
323 lines
12 KiB
Markdown
|
# Contributing to Rebar3
|
||
|
|
||
|
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)
|
||
|
|
||
|
## License ##
|
||
|
|
||
|
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/erlang/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](https://rebar3.org/docs/configuration/plugins#recommended-plugins)
|
||
|
to know if the feature isn't already supported by the community.
|
||
|
- Verify in existing [tickets](https://github.com/erlang/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}.
|
||
|
|
||
|
-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]).
|
||
|
```
|
||
|
|
||
|
Providers are then listed in `rebar.app.src`, and can be called from
|
||
|
the command line or as a programmatical API.
|
||
|
|
||
|
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`.
|
||
|
|
||
|
Templates are included in `priv/templates/`
|
||
|
|
||
|
The official test suite is Common Test, and tests are located in `test/`.
|
||
|
|
||
|
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.
|
||
|
|
||
|
## 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.
|
||
|
|
||
|
Tests are written using the Common Test framework. Tests for rebar3 can be run
|
||
|
by calling:
|
||
|
|
||
|
```bash
|
||
|
$ rebar3 escriptize # or bootstrap
|
||
|
$ ./rebar3 ct
|
||
|
```
|
||
|
|
||
|
Most tests are named according to their module name followed by the `_SUITE`
|
||
|
suffix. 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])}
|
||
|
).
|
||
|
```
|
||
|
|
||
|
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
|
||
|
```
|
||
|
|
||
|
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
|
||
|
|
||
|
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.
|
||
|
|
||
|
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
|
||
|
|
||
|
* 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.
|
||
|
|
||
|
### Committing your changes
|
||
|
|
||
|
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, unless the patch was following a
|
||
|
maintainer's code review. In such cases, it helps to have separate commits.
|
||
|
|
||
|
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
|
||
|
should be no more than 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.
|
||
|
|
||
|
### Pull Requests and Branching
|
||
|
|
||
|
All fixes to rebar end up requiring a +1 from one or more of the project's
|
||
|
maintainers. When opening a pull request, explain what the patch is doing
|
||
|
and if it makes sense, why the proposed implementation was chosen.
|
||
|
|
||
|
Try to use well-defined commits (one feature per commit) so that reading
|
||
|
them and testing them is easier for reviewers and while bisecting the code
|
||
|
base for issues.
|
||
|
|
||
|
During the review process, you may be asked to correct or edit a few things
|
||
|
before a final rebase to merge things. Do 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.
|
||
|
|
||
|
Please work in feature branches, and do not commit to `master` in your fork.
|
||
|
|
||
|
Provide a clean branch without merge commits.
|
||
|
|
||
|
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.
|
||
|
|
||
|
|
||
|
### 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.
|