@ -1,10 +1,21 @@ | |||
language: erlang | |||
otp_release: | |||
- 17.0 | |||
- R16B02 | |||
- R16B01 | |||
- R15B01 | |||
- 17.0 | |||
script: "make travis" | |||
script: make travis | |||
branches: | |||
only: | |||
- rebar3 | |||
- rebar3 | |||
before_deploy: "rm -rf !(rebar3)" | |||
deploy: | |||
on: | |||
branch: rebar3 | |||
condition: $TRAVIS_OTP_RELEASE = R15B01 | |||
provider: s3 | |||
access_key_id: AKIAIKQB2JZYYNIWC72Q | |||
secret_access_key: | |||
secure: JoPSFeISsq7RbkgQLbz/DdLATgGhyuSjjIAwv0lZkhkHspmchArnt9hjDTxmkk8ApPcqDhp8iPEII6E8f2tltTA7djpxx8fO6a7DLyJnDe7QL4Qu1zSMmy91JmkapjDb8VqdAyvUmzAuCp91rfQPsgDiVQNrBSdCfy7LcTZyk3M= | |||
bucket: "rebar3" | |||
skip_cleanup: true |
@ -1,3 +0,0 @@ | |||
rebar_eunit.erl:471: Call to missing or unexported function eunit_test:function_wrapper/2 | |||
rebar_utils.erl:197: Call to missing or unexported function escript:foldl/3 |
@ -0,0 +1,93 @@ | |||
## Why Rebar3? | |||
Rebar was a great step forward for Erlang development but with time has proven to be fragile to change. Rebar3 builds on certain components of rebar which have benefited from years of community collaboration within a new core which is more extendable. | |||
## Creating a New Project | |||
```shell | |||
$ rebar3 new rel myrelease | |||
===> Writing apps/myrelease/src/myrelease_app.erl | |||
===> Writing apps/myrelease/src/myrelease_sup.erl | |||
===> Writing apps/myrelease/src/myrelease.app.src | |||
===> Writing rebar.config | |||
===> Writing relx.config | |||
===> Writing config/sys.config | |||
===> Writing config/vm.args | |||
===> Writing .gitignore | |||
===> Writing LICENSE | |||
===> Writing README.md | |||
``` | |||
## Working on an Existing Rebar3 Project | |||
First, checkout the project and change directories to the project root. | |||
```shell | |||
$ git clone git://github.com/tsloughter/minansan.git | |||
$ cd minansan | |||
``` | |||
Now we can use rebar3 to fetch all dependencies and build both the dependencies and the project app or apps. | |||
```shell | |||
$ rebar3 compile | |||
===> Fetching gproc | |||
Cloning into '.tmp_dir109479658516'... | |||
===> Fetching ranch | |||
Cloning into '.tmp_dir725384773580'... | |||
===> Fetching cowboy | |||
Cloning into '.tmp_dir285325769000'... | |||
===> Fetching cowlib | |||
Cloning into '.tmp_dir924054839613'... | |||
===> Compiling gproc | |||
/home/tristan/Devel/minasan/_deps/gproc/src/gproc_dist.erl:23: Warning: behaviour gen_leader undefined | |||
===> Compiling cowlib | |||
===> Compiling ranch | |||
===> Compiling cowboy | |||
===> Compiling minasan | |||
``` | |||
## Adding Dependencies | |||
Dependencies are listed in `rebar.config` file under the `deps` key: | |||
```erlang | |||
{deps, [ | |||
{cowboy, ".*", {git, "git://github.com/ninenines/cowboy.git", {tag, "1.0.0"}}} | |||
]}. | |||
``` | |||
Then you'll most likely want to add the dependency to one of your project's application's `.app.src` file under applications. | |||
## Rebar3 Conventions | |||
Rebar3 is entirely based on building OTP applications and releases. | |||
* Directories starting with underscores, e.g. `_deps`, are expected to not be checked in version control. | |||
* Project apps you are working on exist under `apps/` or `lib/`, or is a single app project with `src/` in the root directory. | |||
* `rebar.lock` and `rebar.config` go in the root of the project. | |||
* Tests go in `tests/`. | |||
## rebar.config vs rebar.lock | |||
`rebar.lock` contains the exact reference id to a commit that was used to build the project. Committing this file allows you to specify a branch in `rebar.config` for a dependency and still have reproducable builds because if the `rebar.lock` file exists when a rebar3 project is being built the contents of deps in rebar.config are ignored. | |||
## Checkout Dependencies | |||
## Tests | |||
Rebar3 has the concept of test dependencies. These dependencies will only be fetched when a rebar3 command that runs tests is run by the user. | |||
```erlang | |||
{test_deps, [ | |||
{meck, ".*", {git, "https://github.com/eproxus/meck.git", {tag, "0.8"}}} | |||
]}. | |||
``` | |||
```shell | |||
$ rebar ct | |||
===> Fetching meck | |||
Cloning into '.tmp_dir772710363032'... | |||
===> Compiling meck | |||
===> Compiling minasan | |||
``` |
@ -0,0 +1,269 @@ | |||
#### TODO #### | |||
- write a rebar3 template for plugin writing, make it easier on our poor souls | |||
- rework the tutorial to use the rebar3 template for plugins | |||
# Plugins # | |||
Rebar3's system is based on the concept of *[providers](https://github.com/tsloughter/providers)*. A provider has three callbacks: | |||
- `init(State) -> {ok, NewState}`, which helps set up the state required, state dependencies, etc. | |||
- `do(State) -> {ok, NewState} | {error, String}`, which does the actual work. | |||
- `format_error(Error, State) -> {String, NewState}`, which allows to print errors when they happen, and to filter out sensitive elements from the state. | |||
A provider should also be an OTP Library application, which can be fetched as any other Erlang dependency, except for Rebar3 rather than your own system or application. | |||
This document contains the following elements: | |||
- [Using a Plugin](#using-a-plugin) | |||
- [Reference](#reference) | |||
- [Provider Interface](#provider-interface) | |||
- [List of Possible Dependencies](#list-of-possible-dependencies) | |||
- [Rebar State Manipulation](#rebar-state-manipulation) | |||
- [Tutorial](#tutorial) | |||
## Using a Plugin ## | |||
## Reference ## | |||
### Provider Interface ### | |||
### List of Possible Dependencies ### | |||
### Rebar State Manipulation ### | |||
## Tutorial ## | |||
### First version ### | |||
In this tutorial, we'll show how to start from scratch, and get a basic plugin written. The plugin will be quite simple: it will look for instances of 'TODO:' lines in comments and report them as warnings. The final code for the plugin can be found on [bitbucket](https://bitbucket.org/ferd/rebar3-todo-plugin). | |||
The first step is to create a new OTP Application that will contain the plugin: | |||
→ git init | |||
Initialized empty Git repository in /Users/ferd/code/self/rebar3-todo-plugin/.git/ | |||
→ mkdir src | |||
→ touch src/provider_todo.erl src/provider_todo.app.src | |||
Let's edit the app file to make sure the description is fine: | |||
```erlang | |||
{application, provider_todo, [ | |||
{description, "example rebar3 plubin"}, | |||
{vsn, "0.1.0"}, | |||
{registered, []}, | |||
{applications, [kernel, stdlib]}, | |||
{env, []} | |||
]}. | |||
``` | |||
Open up the `provider_todo.erl` file and make sure you have the following skeleton in place: | |||
```erlang | |||
-module(provider_todo). | |||
-behaviour(provider). | |||
-export([init/1, do/1, format_error/2]). | |||
-include_lib("rebar3/include/rebar.hrl"). | |||
-define(PROVIDER, todo). | |||
-define(DEPS, [app_discovery]). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
Provider = providers:create([ | |||
{name, ?PROVIDER}, % The 'user friendly' name of the task | |||
{module, ?MODULE}, % The module implementation of the task | |||
{bare, true}, % The task can be run by the user, always true | |||
{deps, ?DEPS}, % The list of dependencies | |||
{example, "rebar $PLUGIN"}, % How to use the plugin | |||
{opts, []} % list of options understood by the plugin | |||
{short_desc, ""}, | |||
{desc, ""} | |||
]), | |||
{ok, rebar_state:add_provider(State, Provider)}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. | |||
do(State) -> | |||
{ok, State}. | |||
-spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. | |||
format_error(Reason, State) -> | |||
{io_lib:format("~p", [Reason]), State}. | |||
``` | |||
This shows all the basic content needed. Note that we leave the `DEPS` macro to the value `app_discovery`, used to mean that the plugin should at least find the project's source code (excluding dependencies). | |||
In this case, we need to change very little in `init/1`. Here's the new provider description: | |||
```erlang | |||
Provider = providers:create([ | |||
{name, ?PROVIDER}, % The 'user friendly' name of the task | |||
{module, ?MODULE}, % The module implementation of the task | |||
{bare, true}, % The task can be run by the user, always true | |||
{deps, ?DEPS}, % The list of dependencies | |||
{example, "rebar todo"}, % How to use the plugin | |||
{opts, []}, % list of options understood by the plugin | |||
{short_desc, "Reports TODOs in source code"}, | |||
{desc, "Scans top-level application source and find " | |||
"instances of TODO: in commented out content " | |||
"to report it to the user."} | |||
]), | |||
``` | |||
Instead, most of the work will need to be done directly in `do/1`. We'll use the `rebar_state` module to fetch all the applications we need. This can be done by calling the `project_apps/1` function, which returns the list of the project's top-level applications. | |||
```erlang | |||
do(State) -> | |||
lists:foreach(fun check_todo_app/1, rebar_state:project_apps(State)), | |||
{ok, State}. | |||
``` | |||
This, on a high level, means that we'll check each top-level app one at a time (there may often be more than one top-level application when working with releases) | |||
The rest is filler code specific to the plugin, in charge of reading each app path, go read code in there, and find instances of 'TODO:' in comments in the code: | |||
```erlang | |||
check_todo_app(App) -> | |||
Path = filename:join(rebar_app_info:dir(App),"src"), | |||
Mods = find_source_files(Path), | |||
case lists:foldl(fun check_todo_mod/2, [], Mods) of | |||
[] -> ok; | |||
Instances -> display_todos(rebar_app_info:name(App), Instances) | |||
end. | |||
find_source_files(Path) -> | |||
[filename:join(Path, Mod) || Mod <- filelib:wildcard("*.erl", Path)]. | |||
check_todo_mod(ModPath, Matches) -> | |||
{ok, Bin} = file:read_file(ModPath), | |||
case find_todo_lines(Bin) of | |||
[] -> Matches; | |||
Lines -> [{ModPath, Lines} | Matches] | |||
end. | |||
find_todo_lines(File) -> | |||
case re:run(File, "%+.*(TODO:.*)", [{capture, all_but_first, binary}, global, caseless]) of | |||
{match, DeepBins} -> lists:flatten(DeepBins); | |||
nomatch -> [] | |||
end. | |||
display_todos(_, []) -> ok; | |||
display_todos(App, FileMatches) -> | |||
io:format("Application ~s~n",[App]), | |||
[begin | |||
io:format("\t~s~n",[Mod]), | |||
[io:format("\t ~s~n",[TODO]) || TODO <- TODOs] | |||
end || {Mod, TODOs} <- FileMatches], | |||
ok. | |||
``` | |||
Just using `io:format/2` to output is going to be fine. | |||
To test the plugin, push it to a source repository somewhere. Pick one of your projects, and add something to the rebar.config: | |||
```erlang | |||
{plugins, [ | |||
{provider_todo, ".*", {git, "git@bitbucket.org:ferd/rebar3-todo-plugin.git", {branch, "master"}}} | |||
]}. | |||
``` | |||
Then you can just call it directly: | |||
``` | |||
→ rebar3 todo | |||
===> Fetching provider_todo | |||
Cloning into '.tmp_dir539136867963'... | |||
===> Compiling provider_todo | |||
Application merklet | |||
/Users/ferd/code/self/merklet/src/merklet.erl | |||
todo: consider endianness for absolute portability | |||
``` | |||
Rebar3 will download and install the plugin, and figure out when to run it. Once compiled, it can be run at any time again. | |||
### Optionally Search Deps ### | |||
Let's extend things a bit. Maybe from time to time (when cutting a release), we'd like to make sure none of our dependencies contain 'TODO:'s either. | |||
To do this, we'll need to go parse command line arguments a bit, and change our execution model. The `?DEPS` macro will now need to specify that the `todo` provider can only run *after* dependencies have been installed: | |||
```erlang | |||
-define(DEPS, [install_deps]). | |||
``` | |||
We can add the option to the list we use to configure the provider in `init/1`: | |||
```erlang | |||
{opts, [ % list of options understood by the plugin | |||
{deps, $d, "deps", undefined, "also run against dependencies"} | |||
]}, | |||
``` | |||
Meaning that deps can be flagged in by using the option `-d` (or `--deps`), and if it's not defined, well, we get the default value `undefined`. The last element of the 4-tuple is documentation for the option. | |||
And then we can implement the switch to figure out what to search: | |||
```erlang | |||
do(State) -> | |||
Apps = case discovery_type(State) of | |||
project -> rebar_state:project_apps(State); | |||
deps -> rebar_state:project_apps(State) ++ rebar_state:src_deps(State) | |||
end, | |||
lists:foreach(fun check_todo_app/1, Apps), | |||
{ok, State}. | |||
[...] | |||
discovery_type(State) -> | |||
{Args, _} = rebar_state:command_parsed_args(State), | |||
case proplists:get_value(deps, Args) of | |||
undefined -> project; | |||
_ -> deps | |||
end. | |||
``` | |||
The `deps` option is found using `rebar_state:command_parsed_args(State)`, which will return a proplist of terms on the command-line after 'todo', and will take care of validating whether the flags are accepted or not. The rest can remain the same. | |||
Push the new code for the plugin, and try it again on a project with dependencies: | |||
``` | |||
→ rebar3 todo --deps | |||
===> Fetching provider_todo | |||
Cloning into '.tmp_dir846673888664'... | |||
===> Compiling provider_todo | |||
===> Fetching bootstrap | |||
Cloning into '.tmp_dir57833696240'... | |||
===> Fetching file_monitor | |||
Cloning into '.tmp_dir403349997533'... | |||
===> Fetching recon | |||
Cloning into '.tmp_dir390854228780'... | |||
[...] | |||
Application dirmon | |||
/Users/ferd/code/self/figsync/apps/dirmon/src/dirmon_tracker.erl | |||
TODO: Peeranha should expose the UUID from a node. | |||
Application meck | |||
/Users/ferd/code/self/figsync/_deps/meck/src/meck_proc.erl | |||
TODO: What to do here? | |||
TODO: What to do here? | |||
``` | |||
Rebar3 will now go pick dependencies before running the plugin on there. | |||
you can also see that the help will be completed for you: | |||
``` | |||
→ rebar3 help todo | |||
Scans top-level application source and find instances of TODO: in commented out content to report it to the user. | |||
Usage: rebar todo [-d] | |||
-d, --deps also run against dependencies | |||
``` | |||
That's it, the todo plugin is now complete! It's ready to ship and be included in other repositories. |
@ -1,32 +0,0 @@ | |||
{application,rebar, | |||
[{description,"Rebar: Erlang Build Tool"}, | |||
{vsn,"3.0.0"}, | |||
{modules,['LICENSE_dtl','README.md_dtl','app.erl_dtl', | |||
gitignore_dtl,'mod.erl_dtl','otp_app.app.src_dtl', | |||
'otp_lib.app.src_dtl','otp_lib.template_dtl', | |||
'rebar.config_dtl',rebar3,rebar_app_discover, | |||
rebar_app_info,rebar_app_utils,rebar_base_compiler, | |||
rebar_config,rebar_core,rebar_erlc_compiler, | |||
rebar_erlydtl_compiler,rebar_fetch,rebar_file_utils, | |||
rebar_log,rebar_otp_app,rebar_packages,rebar_plugins, | |||
rebar_provider,rebar_prv_app_discovery, | |||
rebar_prv_compile,rebar_prv_deps,rebar_prv_do, | |||
rebar_prv_escripter,rebar_prv_help, | |||
rebar_prv_install_deps,rebar_prv_lock,rebar_prv_new, | |||
rebar_prv_packages,rebar_prv_release,rebar_prv_shell, | |||
rebar_prv_tar,rebar_prv_update,rebar_prv_version, | |||
rebar_state,rebar_templater,rebar_topo,rebar_utils, | |||
'relx.config_dtl','sup.erl_dtl','sys.config_dtl', | |||
'vm.args_dtl']}, | |||
{registered,[]}, | |||
{applications,[kernel,stdlib,sasl,compiler,crypto,syntax_tools, | |||
tools,erlware_commons,relx,inets]}, | |||
{env,[{log_level,warn}, | |||
{providers,[rebar_prv_escripter,rebar_prv_deps, | |||
rebar_prv_do,rebar_prv_lock, | |||
rebar_prv_install_deps,rebar_prv_packages, | |||
rebar_erlydtl_compiler,rebar_prv_compile, | |||
rebar_prv_app_discovery,rebar_prv_shell, | |||
rebar_prv_tar,rebar_prv_new,rebar_prv_update, | |||
rebar_prv_release,rebar_prv_version, | |||
rebar_prv_help]}]}]}. |
@ -0,0 +1,7 @@ | |||
{variables, []}. | |||
{template, "app.erl", "src/{{appid}}_app.erl"}. | |||
{template, "otp_app.app.src", "src/{{appid}}.app.src"}. | |||
{template, "rebar.config", "rebar.config"}. | |||
{template, "gitignore", ".gitignore"}. | |||
{template, "LICENSE", "LICENSE"}. | |||
{template, "README.md", "README.md"}. |
@ -0,0 +1,29 @@ | |||
-module({{appid}}). | |||
-behaviour(rebar_provider). | |||
-export([init/1, | |||
do/1]). | |||
-define(PROVIDER, {{appid}}). | |||
-define(DEPS, []). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
State1 = rebar_state:add_provider(State, rebar_provider:create([{name, ?PROVIDER}, | |||
{provider_impl, ?MODULE}, | |||
{bare, false}, | |||
{deps, ?DEPS}, | |||
{example, "rebar {{appid}}"}, | |||
{short_desc, "{{appid}} plugin."}, | |||
{desc, ""}, | |||
{opts, []}])), | |||
{ok, State1}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
do(State) -> | |||
{ok, State}. |
@ -0,0 +1,7 @@ | |||
{variables, []}. | |||
{template, "plugin.erl", "src/{{appid}}.erl"}. | |||
{template, "otp_lib.app.src", "src/{{appid}}.app.src"}. | |||
{template, "rebar.config", "rebar.config"}. | |||
{template, "gitignore", ".gitignore"}. | |||
{template, "LICENSE", "LICENSE"}. | |||
{template, "plugin_README.md", "README.md"}. |
@ -0,0 +1,14 @@ | |||
{{appid}} | |||
===== | |||
Rebar3 plugin | |||
Build | |||
----- | |||
$ rebar3 compile | |||
Use | |||
--- | |||
$ rebar3 {{appid}} |
@ -1,165 +0,0 @@ | |||
-module(rebar_provider). | |||
%% API | |||
-export([new/2, | |||
do/2, | |||
impl/1, | |||
get_provider/2, | |||
get_target_providers/2, | |||
help/1, | |||
format/1]). | |||
-export_type([t/0]). | |||
-include("rebar.hrl"). | |||
%%%=================================================================== | |||
%%% Types | |||
%%%=================================================================== | |||
-opaque t() :: record(provider). | |||
-type provider_name() :: atom(). | |||
-ifdef(have_callback_support). | |||
-callback init(rebar_state:t()) -> {ok, rebar_state:t()} | relx:error(). | |||
-callback do(rebar_state:t()) -> {ok, rebar_state:t()} | relx:error(). | |||
-else. | |||
%% In the case where R14 or lower is being used to compile the system | |||
%% we need to export a behaviour info | |||
-export([behaviour_info/1]). | |||
-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. | |||
behaviour_info(callbacks) -> | |||
[{init, 1}, | |||
{do, 1}]; | |||
behaviour_info(_) -> | |||
undefined. | |||
-endif. | |||
%%%=================================================================== | |||
%%% API | |||
%%%=================================================================== | |||
%% @doc create a new provider object from the specified module. The | |||
%% module should implement the provider behaviour. | |||
%% | |||
%% @param ModuleName The module name. | |||
%% @param State0 The current state of the system | |||
-spec new(module(), rebar_state:t()) -> | |||
{ok, rebar_state:t()}. | |||
new(ModuleName, State0) when is_atom(ModuleName) -> | |||
case code:which(ModuleName) of | |||
non_existing -> | |||
?ERROR("Module ~p does not exist.", [ModuleName]); | |||
_ -> | |||
ModuleName:init(State0) | |||
end. | |||
%% @doc Manipulate the state of the system, that new state | |||
%% | |||
%% @param Provider the provider object | |||
%% @param State the current state of the system | |||
-spec do(Provider::t(), rebar_state:t()) -> | |||
{ok, rebar_state:t()}. | |||
do(Provider, State) -> | |||
{PreHooks, PostHooks} = rebar_state:hooks(State, Provider#provider.name), | |||
{ok, State1} = run_hook_plugins(PreHooks, State), | |||
{ok, State2} = (Provider#provider.provider_impl):do(State1), | |||
run_hook_plugins(PostHooks, State2). | |||
run_hook_plugins(Hooks, State) -> | |||
State1 = lists:foldl(fun(Hook, StateAcc) -> | |||
{ok, StateAcc1} = rebar_provider:do(Hook, StateAcc), | |||
StateAcc1 | |||
end, State, Hooks), | |||
{ok, State1}. | |||
%%% @doc get the name of the module that implements the provider | |||
%%% @param Provider the provider object | |||
-spec impl(Provider::t()) -> module(). | |||
impl(Provider) -> | |||
Provider#provider.name. | |||
help(State) -> | |||
Providers = rebar_state:providers(State), | |||
Help = lists:sort([{ec_cnv:to_list(P#provider.name), P#provider.short_desc} || P <- Providers, | |||
P#provider.bare =/= true]), | |||
Longest = lists:max([length(X) || {X, _} <- Help]), | |||
lists:foreach(fun({Name, ShortDesc}) -> | |||
Length = length(Name), | |||
Spacing = lists:duplicate(Longest - Length + 8, " "), | |||
io:format("~s~s~s~n", [Name, Spacing, ShortDesc]) | |||
end, Help). | |||
%% @doc print the provider module name | |||
%% | |||
%% @param T - The provider | |||
%% @return An iolist describing the provider | |||
-spec format(t()) -> iolist(). | |||
format(#provider{name=Name}) -> | |||
atom_to_list(Name). | |||
get_target_providers(Target, State) -> | |||
Providers = rebar_state:providers(State), | |||
TargetProviders = lists:filter(fun(#provider{name=T}) when T =:= Target-> | |||
true; | |||
(#provider{name=T}) -> | |||
false | |||
end, Providers), | |||
process_deps(TargetProviders, Providers). | |||
-spec get_provider(provider_name(), [t()]) -> t(). | |||
get_provider(ProviderName, [Provider = #provider{name = ProviderName} | _]) -> | |||
Provider; | |||
get_provider(ProviderName, [_ | Rest]) -> | |||
get_provider(ProviderName, Rest); | |||
get_provider(_ProviderName, _) -> | |||
[]. | |||
process_deps([], _Providers) -> | |||
[]; | |||
process_deps(TargetProviders, Providers) -> | |||
DepChain = lists:flatmap(fun(Provider) -> | |||
{DC, _, _} = process_deps(Provider, Providers, []), | |||
DC | |||
end, TargetProviders), | |||
['NONE' | Rest] = | |||
reorder_providers(lists:flatten([{'NONE', P#provider.name} || P <- TargetProviders] ++ DepChain)), | |||
Rest. | |||
process_deps(Provider, Providers, Seen) -> | |||
case lists:member(Provider, Seen) of | |||
true -> | |||
{[], Providers, Seen}; | |||
false -> | |||
Deps = Provider#provider.deps, | |||
DepList = lists:map(fun(Dep) -> | |||
{Dep, Provider#provider.name} | |||
end, Deps), | |||
{NewDeps, _, NewSeen} = | |||
lists:foldl(fun(Arg, Acc) -> | |||
process_dep(Arg, Acc) | |||
end, | |||
{[], Providers, Seen}, Deps), | |||
{[DepList | NewDeps], Providers, NewSeen} | |||
end. | |||
process_dep(ProviderName, {Deps, Providers, Seen}) -> | |||
Provider = get_provider(ProviderName, Providers), | |||
{NewDeps, _, NewSeen} = process_deps(Provider, Providers, [ProviderName | Seen]), | |||
{[Deps | NewDeps], Providers, NewSeen}. | |||
%% @doc Reorder the providers according to thier dependency set. | |||
reorder_providers(OProviderList) -> | |||
case rebar_topo:sort(OProviderList) of | |||
{ok, ProviderList} -> | |||
ProviderList; | |||
{cycle, _} -> | |||
?ERROR("There was a cycle in the provider list. Unable to complete build!", []) | |||
end. |
@ -0,0 +1,44 @@ | |||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- | |||
%% ex: ts=4 sw=4 et | |||
-module(rebar_prv_clean). | |||
-behaviour(provider). | |||
-export([init/1, | |||
do/1, | |||
format_error/2]). | |||
-include("rebar.hrl"). | |||
-define(PROVIDER, clean). | |||
-define(DEPS, [app_discovery]). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, | |||
{module, ?MODULE}, | |||
{bare, false}, | |||
{deps, ?DEPS}, | |||
{example, "rebar clean"}, | |||
{short_desc, "Remove compiled beam files from apps."}, | |||
{desc, ""}, | |||
{opts, []}])), | |||
{ok, State1}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. | |||
do(State) -> | |||
ProjectApps = rebar_state:project_apps(State), | |||
lists:foreach(fun(AppInfo) -> | |||
?INFO("Cleaning out ~s...~n", [rebar_app_info:name(AppInfo)]), | |||
rebar_erlc_compiler:clean(State, ec_cnv:to_list(rebar_app_info:dir(AppInfo))) | |||
end, ProjectApps), | |||
{ok, State}. | |||
-spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. | |||
format_error(Reason, State) -> | |||
{io_lib:format("~p", [Reason]), State}. |
@ -1,224 +0,0 @@ | |||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- | |||
%% ex: ts=4 sw=4 et | |||
%% ------------------------------------------------------------------- | |||
%% | |||
%% rebar: Erlang Build Tools | |||
%% | |||
%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) | |||
%% | |||
%% Permission is hereby granted, free of charge, to any person obtaining a copy | |||
%% of this software and associated documentation files (the "Software"), to deal | |||
%% in the Software without restriction, including without limitation the rights | |||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
%% copies of the Software, and to permit persons to whom the Software is | |||
%% furnished to do so, subject to the following conditions: | |||
%% | |||
%% The above copyright notice and this permission notice shall be included in | |||
%% all copies or substantial portions of the Software. | |||
%% | |||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
%% THE SOFTWARE. | |||
%% ------------------------------------------------------------------- | |||
-module(rebar_prv_escripter). | |||
-behaviour(rebar_provider). | |||
-export([init/1, | |||
do/1]). | |||
-export([escriptize/2, | |||
clean/2]). | |||
%% for internal use only | |||
-export([info/2]). | |||
-include("rebar.hrl"). | |||
-include_lib("kernel/include/file.hrl"). | |||
-define(PROVIDER, escriptize). | |||
-define(DEPS, [app_builder]). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
State1 = rebar_state:add_provider(State, #provider{name = ?PROVIDER, | |||
provider_impl = ?MODULE, | |||
bare = false, | |||
deps = ?DEPS, | |||
example = "rebar escriptize", | |||
short_desc = "Build escript from project.", | |||
desc = "", | |||
opts = []}), | |||
{ok, State1}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | relx:error(). | |||
do(Config) -> | |||
AppName = rebar_state:get_local(Config, escript_top_level_app, undefined), | |||
App = rebar_state:get_app(Config, AppName), | |||
{ok, Config1} = escriptize(Config, rebar_app_info:app_file(App)), | |||
{ok, Config1}. | |||
escriptize(Config0, AppFile) -> | |||
%% Extract the application name from the archive -- this is the default | |||
%% name of the generated script | |||
{Config, AppName} = rebar_app_utils:app_name(Config0, AppFile), | |||
AppNameStr = atom_to_list(AppName), | |||
%% Get the output filename for the escript -- this may include dirs | |||
Filename = rebar_state:get_local(Config, escript_name, AppName), | |||
ok = filelib:ensure_dir(Filename), | |||
%% Look for a list of other applications (dependencies) to include | |||
%% in the output file. We then use the .app files for each of these | |||
%% to pull in all the .beam files. | |||
InclBeams = get_app_beams( | |||
rebar_state:get_local(Config, escript_incl_apps, []), []), | |||
%% Look for a list of extra files to include in the output file. | |||
%% For internal rebar-private use only. Do not use outside rebar. | |||
InclExtra = get_extra(Config), | |||
%% Construct the archive of everything in ebin/ dir -- put it on the | |||
%% top-level of the zip file so that code loading works properly. | |||
EbinPrefix = filename:join(AppNameStr, "ebin"), | |||
EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")), | |||
ExtraFiles = usort(InclBeams ++ InclExtra), | |||
Files = EbinFiles ++ ExtraFiles, | |||
case zip:create("mem", Files, [memory]) of | |||
{ok, {"mem", ZipBin}} -> | |||
%% Archive was successfully created. Prefix that binary with our | |||
%% header and write to our escript file | |||
Shebang = rebar_state:get(Config, escript_shebang, | |||
"#!/usr/bin/env escript\n"), | |||
Comment = rebar_state:get(Config, escript_comment, "%%\n"), | |||
DefaultEmuArgs = ?FMT("%%! -pa ~s/~s/ebin\n", | |||
[AppNameStr, AppNameStr]), | |||
EmuArgs = rebar_state:get(Config, escript_emu_args, | |||
DefaultEmuArgs), | |||
Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]), | |||
case file:write_file(Filename, Script) of | |||
ok -> | |||
ok; | |||
{error, WriteError} -> | |||
?ERROR("Failed to write ~p script: ~p\n", | |||
[AppName, WriteError]), | |||
?FAIL | |||
end; | |||
{error, ZipError} -> | |||
?ERROR("Failed to construct ~p escript: ~p\n", | |||
[AppName, ZipError]), | |||
?FAIL | |||
end, | |||
%% Finally, update executable perms for our script | |||
{ok, #file_info{mode = Mode}} = file:read_file_info(Filename), | |||
ok = file:change_mode(Filename, Mode bor 8#00111), | |||
{ok, Config}. | |||
clean(Config0, AppFile) -> | |||
%% Extract the application name from the archive -- this is the default | |||
%% name of the generated script | |||
{Config, AppName} = rebar_app_utils:app_name(Config0, AppFile), | |||
%% Get the output filename for the escript -- this may include dirs | |||
Filename = rebar_state:get_local(Config, escript_name, AppName), | |||
rebar_file_utils:delete_each([Filename]), | |||
{ok, Config}. | |||
%% =================================================================== | |||
%% Internal functions | |||
%% =================================================================== | |||
info(help, escriptize) -> | |||
info_help("Generate escript archive"); | |||
info(help, clean) -> | |||
info_help("Delete generated escript archive"). | |||
info_help(Description) -> | |||
?CONSOLE( | |||
"~s.~n" | |||
"~n" | |||
"Valid rebar.config options:~n" | |||
" ~p~n" | |||
" ~p~n" | |||
" ~p~n" | |||
" ~p~n" | |||
" ~p~n", | |||
[ | |||
Description, | |||
{escript_name, "application"}, | |||
{escript_incl_apps, []}, | |||
{escript_shebang, "#!/usr/bin/env escript\n"}, | |||
{escript_comment, "%%\n"}, | |||
{escript_emu_args, "%%! -pa application/application/ebin\n"} | |||
]). | |||
get_app_beams([], Acc) -> | |||
Acc; | |||
get_app_beams([App | Rest], Acc) -> | |||
case code:lib_dir(App, ebin) of | |||
{error, bad_name} -> | |||
?ABORT("Failed to get ebin/ directory for " | |||
"~p escript_incl_apps.", [App]); | |||
Path -> | |||
Prefix = filename:join(atom_to_list(App), "ebin"), | |||
Acc2 = load_files(Prefix, "*", Path), | |||
get_app_beams(Rest, Acc2 ++ Acc) | |||
end. | |||
get_extra(Config) -> | |||
Extra = rebar_state:get_local(Config, escript_incl_extra, []), | |||
lists:foldl(fun({Wildcard, Dir}, Files) -> | |||
load_files(Wildcard, Dir) ++ Files | |||
end, [], Extra). | |||
load_files(Wildcard, Dir) -> | |||
load_files("", Wildcard, Dir). | |||
load_files(Prefix, Wildcard, Dir) -> | |||
[read_file(Prefix, Filename, Dir) | |||
|| Filename <- filelib:wildcard(Wildcard, Dir)]. | |||
read_file(Prefix, Filename, Dir) -> | |||
Filename1 = case Prefix of | |||
"" -> | |||
Filename; | |||
_ -> | |||
filename:join([Prefix, Filename]) | |||
end, | |||
[dir_entries(filename:dirname(Filename1)), | |||
{Filename1, file_contents(filename:join(Dir, Filename))}]. | |||
file_contents(Filename) -> | |||
{ok, Bin} = file:read_file(Filename), | |||
Bin. | |||
%% Given a filename, return zip archive dir entries for each sub-dir. | |||
%% Required to work around issues fixed in OTP-10071. | |||
dir_entries(File) -> | |||
Dirs = dirs(File), | |||
[{Dir ++ "/", <<>>} || Dir <- Dirs]. | |||
%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"]. | |||
dirs(Dir) -> | |||
dirs1(filename:split(Dir), "", []). | |||
dirs1([], _, Acc) -> | |||
lists:reverse(Acc); | |||
dirs1([H|T], "", []) -> | |||
dirs1(T, H, [H]); | |||
dirs1([H|T], Last, Acc) -> | |||
Dir = filename:join(Last, H), | |||
dirs1(T, Dir, [Dir|Acc]). | |||
usort(List) -> | |||
lists:ukeysort(1, lists:flatten(List)). |
@ -0,0 +1,63 @@ | |||
-module(rebar_prv_test_deps). | |||
-behaviour(provider). | |||
-export([init/1, | |||
do/1, | |||
format_error/2]). | |||
-include("rebar.hrl"). | |||
-define(PROVIDER, test_deps). | |||
-define(DEPS, [install_deps]). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
Providers = rebar_state:providers(State), | |||
CompileProvider = providers:get_provider(compile, Providers), | |||
State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, | |||
{module, ?MODULE}, | |||
{bare, true}, | |||
{deps, ?DEPS}, | |||
{hooks, {[], [CompileProvider]}}, | |||
{example, undefined}, | |||
{short_desc, "Install dependencies needed only for testing."}, | |||
{desc, ""}, | |||
{opts, []}])), | |||
{ok, State1}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. | |||
do(State) -> | |||
ProjectApps = rebar_state:project_apps(State), | |||
TestDeps = rebar_state:get(State, test_deps, []), | |||
Names = [ec_cnv:to_binary(element(1, Dep)) || Dep <- TestDeps], | |||
ProjectApps1 = [rebar_app_info:deps(A, Names) || A <- ProjectApps], | |||
{ok, State1} = rebar_prv_install_deps:handle_deps(State, TestDeps), | |||
AllDeps = rebar_state:get(State1, all_deps, []), | |||
case rebar_topo:sort_apps(ProjectApps1++AllDeps) of | |||
{ok, Sort} -> | |||
_ToBuild = lists:dropwhile(fun rebar_app_info:valid/1, Sort), | |||
%% lists:foreach(fun(AppInfo) -> | |||
%% AppDir = rebar_app_info:dir(AppInfo), | |||
%% C = rebar_config:consult(AppDir), | |||
%% S = rebar_state:new(State1, C, AppDir), | |||
%% rebar_prv_compile:build(S, AppInfo) | |||
%% end, ToBuild), | |||
{ok, State1}; | |||
{error, Error} -> | |||
{error, Error} | |||
end. | |||
-spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. | |||
format_error(Reason, State) -> | |||
{io_lib:format("~p", [Reason]), State}. | |||
%% =================================================================== | |||
%% Internal functions | |||
%% =================================================================== |
@ -0,0 +1,55 @@ | |||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- | |||
%% ex: ts=4 sw=4 et | |||
-module(rebar_prv_upgrade). | |||
-behaviour(provider). | |||
-export([init/1, | |||
do/1, | |||
format_error/2]). | |||
-include("rebar.hrl"). | |||
-define(PROVIDER, upgrade). | |||
-define(DEPS, [lock]). | |||
%% =================================================================== | |||
%% Public API | |||
%% =================================================================== | |||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||
init(State) -> | |||
State1 = | |||
rebar_state:add_provider(State, | |||
providers:create([{name, ?PROVIDER}, | |||
{module, ?MODULE}, | |||
{bare, false}, | |||
{deps, ?DEPS}, | |||
{example, "rebar upgrade cowboy"}, | |||
{short_desc, "Upgrade dependency."}, | |||
{desc, ""}, | |||
{opts, [ | |||
{package, undefined, undefined, string, "Package to upgrade."} | |||
]}])), | |||
{ok, State1}. | |||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. | |||
do(State) -> | |||
{Args, _} = rebar_state:command_parsed_args(State), | |||
Name = proplists:get_value(package, Args), | |||
?INFO("Updating ~s~n", [Name]), | |||
Locks = rebar_state:get(State, locks, []), | |||
case lists:keyfind(ec_cnv:to_binary(Name), 1, Locks) of | |||
{_, _, _, Level} -> | |||
Deps = rebar_state:get(State, deps), | |||
Dep = lists:keyfind(list_to_atom(Name), 1, Deps), | |||
rebar_prv_install_deps:handle_deps(State, [Dep], {true, ec_cnv:to_binary(Name), Level}), | |||
{ok, State}; | |||
_ -> | |||
{error, io_lib:format("No such dependency ~s~n", [Name])} | |||
end. | |||
-spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. | |||
format_error(Reason, State) -> | |||
{io_lib:format("~p", [Reason]), State}. |