diff options
author | Tristan Sloughter <t@crashfast.com> | 2014-07-27 18:36:31 -0500 |
---|---|---|
committer | Tristan Sloughter <t@crashfast.com> | 2014-08-16 07:22:27 -0500 |
commit | eb8fa02df7d71435a879de987b3139bb5bffb963 (patch) | |
tree | e3ac360353b37811be1a3775d986e4a3ceab16e2 | |
parent | 19c215ee9fe0726a1983b36f4f8bcc21d42a5ef8 (diff) |
large refactoring
Removed separate compilers
Resolves apps to build
Finds avail deps before pulling/building
Includes relx
Simplifies build commands
41 files changed, 1511 insertions, 4272 deletions
@@ -9,3 +9,4 @@ /.eunit /deps /.rebar +rebar.lock @@ -4,7 +4,7 @@ REBAR=$(PWD)/rebar RETEST=$(PWD)/deps/retest/retest all: - ./bootstrap + ./bootstrap/bootstrap clean: @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .eunit @@ -15,7 +15,7 @@ distclean: clean @rm -rf deps debug: - @./bootstrap debug + @./bootstrap/bootstrap debug check: debug xref dialyzer deps test @@ -13,6 +13,80 @@ configuration work. rebar also provides dependency management, enabling application writers to easily re-use common libraries from a variety of locations (git, hg, etc). +3.0 +==== + +This is an experimental branch. + +### Commands + +| Command | Description | +|----------- |------------ | +| compile | Build project | +| shell | Run shell with project apps in path | +| escriptize | Create escript from project | +| release | Build release of project | +| tar | Package release into tarball | + +### Missing + +* Pre and post hooks +* Compilers besides erlc + +### Changes + +* Fetches and builds deps if missing when running any command that relies on them +* Automatically recognizes `apps` and `libs` directory structure +* `escriptize` requires `escript_top_level_app` set in `rebar.config` +* Relx for releases + +### Gone + +* Reltool integeration + +### 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]). + +-include("rebar.hrl"). + +-define(PROVIDER, something). +-define(DEPS, []). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = something, + bare = false, + deps = ?DEPS, + example = "rebar something", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + %% Do something + {ok, Config}. +``` + + Building -------- @@ -37,7 +111,7 @@ https://github.com/rebar/rebar/wiki/rebar ```sh $ git clone git://github.com/rebar/rebar.git $ cd rebar -$ ./bootstrap +$ ./bootstrap/bootstrap Recompile: src/getopt ... Recompile: src/rebar_utils diff --git a/bootstrap b/bootstrap/bootstrap index 7d9a1c1..18a243f 100755 --- a/bootstrap +++ b/bootstrap/bootstrap @@ -46,6 +46,8 @@ main(Args) -> false -> undefined end, + os:cmd("./bootstrap/rebar get-deps compile -r"), + %% Compile all src/*.erl to ebin %% To not accidentally try to compile files like Mac OS X resource forks, %% we only look for rebar source files that start with a letter. diff --git a/bootstrap.bat b/bootstrap/bootstrap.bat index b646a7d..b646a7d 100644 --- a/bootstrap.bat +++ b/bootstrap/bootstrap.bat diff --git a/bootstrap/rebar b/bootstrap/rebar Binary files differnew file mode 100755 index 0000000..14e5c22 --- /dev/null +++ b/bootstrap/rebar diff --git a/ebin/rebar.app b/ebin/rebar.app index 9ee54e6..5c5cc70 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -3,13 +3,11 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "2.5.0"}, + {vsn, "3.0.0"}, {modules, [ rebar, - rebar_abnfc_compiler, rebar_app_utils, - rebar_appups, - rebar_asn1_compiler, - rebar_dia_compiler, + rebar_app_info, + rebar_app_discover, rebar_base_compiler, rebar_cleaner, rebar_config, @@ -22,26 +20,23 @@ rebar_erlydtl_compiler, rebar_escripter, rebar_eunit, + rebar_fetch, rebar_file_utils, - rebar_lfe_compiler, rebar_log, - rebar_neotoma_compiler, rebar_otp_app, - rebar_port_compiler, - rebar_protobuffs_compiler, + rebar_provider, + rebar_prv_app_builder, rebar_qc, - rebar_rel_utils, - rebar_reltool, rebar_require_vsn, + rebar_prv_release, rebar_shell, rebar_subdirs, + rebar_prv_tar, rebar_templater, - rebar_upgrade, + rebar_topo, rebar_utils, rebar_xref, - rebar_metacmds, - rebar_getopt, - rebar_mustache ]}, + rebar_getopt]}, {registered, []}, {applications, [kernel, stdlib, @@ -49,56 +44,19 @@ compiler, crypto, syntax_tools, - tools]}, + tools, + relx]}, {env, [ - %% Default log level - {log_level, warn}, + %% Default log level + {log_level, warn}, - %% any_dir processing modules - {any_dir_modules, [ - rebar_require_vsn, - rebar_deps, - rebar_subdirs, - rebar_templater, - rebar_cleaner - ]}, - - %% Dir specific processing modules - {modules, [ - {app_dir, [ - rebar_abnfc_compiler, - rebar_protobuffs_compiler, - rebar_neotoma_compiler, - rebar_asn1_compiler, - rebar_dia_compiler, - rebar_erlc_compiler, - rebar_lfe_compiler, - rebar_erlydtl_compiler, - rebar_port_compiler, - rebar_otp_app, - rebar_ct, - rebar_eunit, - rebar_qc, - rebar_escripter, - rebar_edoc, - rebar_shell, - rebar_xref, - rebar_metacmds - ]}, - - {rel_dir, [ - rebar_appups, - rebar_reltool, - rebar_upgrade - ]} - ]}, - {recursive_cmds, [ - 'check-deps', - compile, - 'delete-deps', - 'get-deps', - 'list-deps', - 'update-deps' - ]} + %% any_dir processing modules + {providers, [rebar_escripter, + rebar_deps, + rebar_erlydtl_compiler, + rebar_prv_app_builder, + rebar_shell, + rebar_prv_tar, + rebar_prv_release]} ]} ]}. diff --git a/include/rebar.hrl b/include/rebar.hrl index b19fdd3..a905569 100644 --- a/include/rebar.hrl +++ b/include/rebar.hrl @@ -12,3 +12,13 @@ -define(ERROR(Str, Args), rebar_log:log(standard_error, error, Str, Args)). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). + +-record(provider, {name :: atom(), % The 'user friendly' name of the task + provider_impl :: atom(), % The implementation of the task, maybe fun or + provides :: atom(), + bare :: boolean(), % Indicates whether a build config is needed + deps :: [atom()], % The list of dependencies + desc :: string(), % The description for the task + short_desc :: string(), % A one line short description of the task + example :: string(), % An example of the task usage + opts :: list()}). % The list of options that the task requires/understands diff --git a/rebar.config b/rebar.config index 1c62a55..b959579 100644 --- a/rebar.config +++ b/rebar.config @@ -4,6 +4,9 @@ %% escript_incl_extra is for internal rebar-private use only. %% Do not use outside rebar. Config interface is not stable. {escript_incl_extra, [{"priv/templates/*", "."}]}. +{escript_incl_apps, + [getopt, erlydtl, erlware_commons, relx]}. +{escript_top_level_app, rebar}. %% Types dict:dict() and digraph:digraph() have been introduced in Erlang 17. %% At the same time, their counterparts dict() and digraph() are to be @@ -28,3 +31,9 @@ - (\"diameter_dict_util\":\"format_error\"/\"1\") - (\"diameter_dict_util\":\"parse\"/\"2\"))", []}]}. + +{first_files, [rebar_provider]}. + +{deps, [{relx, "", + {git, "https://github.com/erlware/relx.git", + {branch, "master"}}}]}. diff --git a/src/rebar.erl b/src/rebar.erl index a43da5f..4189b91 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -145,10 +145,8 @@ init_config({Options, _NonOptArgs}) -> BaseConfig = rebar_config:base_config(GlobalConfig2), - %% Keep track of how many operations we do, so we can detect bad commands - BaseConfig1 = rebar_config:set_xconf(BaseConfig, operations, 0), %% Initialize vsn cache - rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()). + rebar_config:set_xconf(BaseConfig, vsn_cache, dict:new()). init_config1(BaseConfig) -> %% Determine the location of the rebar executable; important for pulling @@ -173,7 +171,10 @@ run_aux(BaseConfig, Commands) -> BaseConfig1 = init_config1(BaseConfig), %% Process each command, resetting any state between each one - rebar_core:process_commands(CommandAtoms, BaseConfig1). + {ok, Providers} = application:get_env(rebar, providers), + BaseConfig2 = rebar_config:create_logic_providers(Providers, BaseConfig1), + rebar_core:process_commands(CommandAtoms, BaseConfig2), + ok. %% %% print help/usage string @@ -382,13 +383,6 @@ update-deps Update fetched dependencies delete-deps Delete fetched dependencies list-deps List dependencies -generate [dump_spec=0/1] Build release with reltool -overlay Run reltool overlays only - -generate-upgrade previous_release=path Build an upgrade package - -generate-appups previous_release=path Generate appup files - eunit [suite[s]=foo] Run EUnit tests in foo.erl and test/foo_tests.erl [suite[s]=foo] [test[s]=bar] Run specific EUnit tests [first test @@ -476,6 +470,7 @@ command_names() -> "check-deps", "clean", "compile", + "release", "create", "create-app", "create-lib", @@ -485,9 +480,6 @@ command_names() -> "doc", "eunit", "escriptize", - "generate", - "generate-appups", - "generate-upgrade", "get-deps", "help", "list-deps", diff --git a/src/rebar_abnfc_compiler.erl b/src/rebar_abnfc_compiler.erl deleted file mode 100644 index 37731b5..0000000 --- a/src/rebar_abnfc_compiler.erl +++ /dev/null @@ -1,123 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2010 Anthony Ramine (nox@dev-extend.eu), -%% -%% 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. -%% ------------------------------------------------------------------- - -%% The rebar_abnfc_compiler module is a plugin for rebar that compiles -%% ABNF grammars into parsers. By default, it compiles all src/*.abnf -%% to src/*.erl. -%% -%% Configuration options should be placed in rebar.config under -%% 'abnfc_opts'. Available options include: -%% -%% doc_root: where to find the ABNF grammars to compile -%% "src" by default -%% -%% out_dir: where to put the generated files. -%% "src" by default -%% -%% source_ext: the file extension the ABNF grammars have. -%% ".abnf" by default -%% -%% module_ext: characters to append to the parser's module name -%% "" by default --module(rebar_abnfc_compiler). - --export([compile/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - -compile(Config, _AppFile) -> - DtlOpts = abnfc_opts(Config), - rebar_base_compiler:run(Config, [], - option(doc_root, DtlOpts), - option(source_ext, DtlOpts), - option(out_dir, DtlOpts), - option(module_ext, DtlOpts) ++ ".erl", - fun compile_abnfc/3). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - ?CONSOLE( - "Build ABNF (*.abnf) sources.~n" - "~n" - "Valid rebar.config options:~n" - " ~p~n", - [ - {abnfc_opts, [{doc_root, "src"}, - {out_dir, "src"}, - {source_ext, ".abnfc"}, - {module_ext, ""}]} - ]). - -abnfc_opts(Config) -> - rebar_config:get(Config, abnfc_opts, []). - -option(Opt, DtlOpts) -> - proplists:get_value(Opt, DtlOpts, default(Opt)). - -default(doc_root) -> "src"; -default(out_dir) -> "src"; -default(source_ext) -> ".abnf"; -default(module_ext) -> "". - -abnfc_is_present() -> - code:which(abnfc) =/= non_existing. - -compile_abnfc(Source, _Target, Config) -> - case abnfc_is_present() of - false -> - ?ERROR("~n===============================================~n" - " You need to install abnfc to compile ABNF grammars~n" - " Download the latest tarball release from github~n" - " https://github.com/nygge/abnfc~n" - " and install it into your erlang library dir~n" - "===============================================~n~n", []), - ?FAIL; - true -> - AbnfcOpts = abnfc_opts(Config), - SourceExt = option(source_ext, AbnfcOpts), - Opts = [noobj, - {o, option(out_dir, AbnfcOpts)}, - {mod, filename:basename(Source, SourceExt) ++ - option(module_ext, AbnfcOpts)}], - case abnfc:file(Source, Opts) of - ok -> ok; - Error -> - ?ERROR("Compiling grammar ~s failed:~n ~p~n", - [Source, Error]), - ?FAIL - end - end. diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl new file mode 100644 index 0000000..4252262 --- /dev/null +++ b/src/rebar_app_discover.erl @@ -0,0 +1,84 @@ +-module(rebar_app_discover). + +-export([do/2, + find_apps/1]). + +do(Config, LibDirs) -> + Apps = find_apps(LibDirs), + lists:foldl(fun(AppInfo, ConfigAcc) -> + rebar_config:add_app(ConfigAcc, AppInfo) + end, Config, Apps). + +-spec all_app_dirs(list(file:name())) -> list(file:name()). +all_app_dirs(LibDirs) -> + lists:flatmap(fun(LibDir) -> + app_dirs(LibDir) + end, LibDirs). + +app_dirs(LibDir) -> + Path1 = filename:join([LibDir, + "*", + "src", + "*.app.src"]), + Path2 = filename:join([LibDir, + "src", + "*.app.src"]), + + Path3 = filename:join([LibDir, + "*", + "ebin", + "*.app"]), + Path4 = filename:join([LibDir, + "ebin", + "*.app"]), + + lists:usort(lists:foldl(fun(Path, Acc) -> + Files = filelib:wildcard(Path), + [app_dir(File) || File <- Files] ++ Acc + end, [], [Path1, Path2, Path3, Path4])). + +find_apps(LibDirs) -> + lists:map(fun(AppDir) -> + AppFile = filelib:wildcard(filename:join([AppDir, "ebin", "*.app"])), + AppSrcFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src"])), + case AppFile of + [File] -> + AppInfo = create_app_info(AppDir, File), + AppInfo1 = rebar_app_info:app_file(AppInfo, File), + case AppSrcFile of + [F] -> + rebar_app_info:app_file_src(AppInfo1, F); + [] -> + AppInfo1 + end; + [] -> + case AppSrcFile of + [File] -> + AppInfo = create_app_info(AppDir, File), + rebar_app_info:app_file_src(AppInfo, File); + [] -> + error + end + end + end, all_app_dirs(LibDirs)). + +app_dir(AppFile) -> + filename:join(lists:droplast(filename:split(filename:dirname(AppFile)))). + +create_app_info(AppDir, AppFile) -> + case file:consult(AppFile) of + {ok, [{application, AppName, AppDetails}]} -> + AppVsn = proplists:get_value(vsn, AppDetails), + AbsCwd = filename:absname(rebar_utils:get_cwd()), + {ok, AppInfo} = rebar_app_info:new(AppName, AppVsn, AppDir), + RebarConfig = filename:join(AppDir, "rebar.config"), + AppConfig = case filelib:is_file(RebarConfig) of + true -> + rebar_config:new(RebarConfig); + false -> + rebar_config:new() + end, + AppConfig1 = rebar_config:set_xconf(AppConfig, base_dir, AbsCwd), + AppInfo1 = rebar_app_info:config(AppInfo, AppConfig1), + rebar_app_info:dir(AppInfo1, AppDir) + end. diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl new file mode 100644 index 0000000..503d3bc --- /dev/null +++ b/src/rebar_app_info.erl @@ -0,0 +1,92 @@ +-module(rebar_app_info). + +-export([new/0, + new/3, + name/1, + name/2, + config/1, + config/2, + app_file_src/1, + app_file_src/2, + app_file/1, + app_file/2, + original_vsn/1, + dir/1, + dir/2]). + +-export_type([t/0]). + +-record(app_info_t, {name :: atom(), + app_file_src :: file:name() | undefined, + app_file :: file:name(), + config :: rebar_config:config() | undefined, + original_vsn :: string(), + dir :: file:name(), + source :: string() | undefined}). + +%%============================================================================ +%% types +%%============================================================================ +-opaque t() :: record(app_info_t). + +%%============================================================================ +%% API +%% ============================================================================ +%% @doc Build a new, empty, app info value. This is not of a lot of use and you +%% probably wont be doing this much. +-spec new() -> {ok, t()}. +new() -> + {ok, #app_info_t{}}. + +%% @doc build a complete version of the app info with all fields set. +-spec new(atom(), string(), file:name()) -> + {ok, t()}. +new(AppName, Vsn, Dir) + when erlang:is_atom(AppName) -> + {ok, #app_info_t{name=AppName, + original_vsn=Vsn, + dir=Dir}}. + +-spec name(t()) -> atom(). +name(#app_info_t{name=Name}) -> + Name. + +-spec name(t(), atom()) -> t(). +name(AppInfo=#app_info_t{}, AppName) + when erlang:is_atom(AppName) -> + AppInfo#app_info_t{name=AppName}. + +-spec config(t()) -> rebar_config:confg(). +config(#app_info_t{config=Config}) -> + Config. + +-spec config(t(), rebar_config:confg()) -> t(). +config(AppInfo=#app_info_t{}, Config) -> + AppInfo#app_info_t{config=Config}. + +-spec app_file_src(t()) -> file:name(). +app_file_src(#app_info_t{app_file_src=AppFileSrc}) -> + AppFileSrc. + +-spec app_file_src(t(), file:name()) -> t(). +app_file_src(AppInfo=#app_info_t{}, AppFileSrc) -> + AppInfo#app_info_t{app_file_src=AppFileSrc}. + +-spec app_file(t()) -> file:name(). +app_file(#app_info_t{app_file=AppFile}) -> + AppFile. + +-spec app_file(t(), file:name()) -> t(). +app_file(AppInfo=#app_info_t{}, AppFile) -> + AppInfo#app_info_t{app_file=AppFile}. + +-spec original_vsn(t()) -> string(). +original_vsn(#app_info_t{original_vsn=Vsn}) -> + Vsn. + +-spec dir(t()) -> file:name(). +dir(#app_info_t{dir=Dir}) -> + Dir. +-spec dir(t(), file:name()) -> t(). +dir(AppInfo=#app_info_t{}, Dir) -> + AppInfo#app_info_t{dir=Dir}. diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl index a2484e1..bbcbaf4 100644 --- a/src/rebar_app_utils.erl +++ b/src/rebar_app_utils.erl @@ -75,7 +75,10 @@ is_app_src(Filename) -> Filename =/= filename:rootname(Filename, ".app.src"). app_src_to_app(Filename) -> - filename:join("ebin", filename:basename(Filename, ".app.src") ++ ".app"). + Path = filename:join(lists:droplast(filename:split(filename:dirname(Filename)))), + AppFile = filename:join([Path, "ebin", filename:basename(Filename, ".app.src") ++ ".app"]), + filelib:ensure_dir(AppFile), + AppFile. app_name(Config, AppFile) -> case load_app_file(Config, AppFile) of diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl deleted file mode 100644 index 38e7b72..0000000 --- a/src/rebar_appups.erl +++ /dev/null @@ -1,219 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2011 Joe Williams (joe@joetify.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_appups). - --include("rebar.hrl"). - --export(['generate-appups'/2]). - -%% for internal use only --export([info/2]). - --define(APPUPFILEFORMAT, "%% appup generated for ~p by rebar (~p)~n" - "{~p, [{~p, ~p}], [{~p, []}]}.~n"). - -%% ==================================================================== -%% Public API -%% ==================================================================== - -'generate-appups'(Config, ReltoolFile) -> - %% Get the old release path - {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile), - TargetParentDir = rebar_rel_utils:get_target_parent_dir(Config, - ReltoolConfig), - - PrevRelPath = rebar_rel_utils:get_previous_release_path(Config), - OldVerPath = filename:join([TargetParentDir, PrevRelPath]), - - ModDeps = rebar_config:get(Config, module_deps, []), - - %% Get the new and old release name and versions - {Name, _Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - NewVerPath = filename:join([TargetParentDir, Name]), - {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath), - {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), - - %% Run some simple checks - true = rebar_utils:prop_check(NewVer =/= OldVer, - "New and old .rel versions match~n", []), - true = rebar_utils:prop_check( - NewName == OldName, - "Reltool and .rel release names do not match~n", []), - - %% Find all the apps that have been upgraded - {_Added, _Removed, Upgraded} = get_apps(Name, OldVerPath, NewVerPath), - - %% Get a list of any appup files that exist in the new release - NewAppUpFiles = rebar_utils:find_files( - filename:join([NewVerPath, "lib"]), "^[^._].*.appup$"), - - %% Convert the list of appup files into app names - AppUpApps = [file_to_name(File) || File <- NewAppUpFiles], - - %% Create a list of apps that don't already have appups - UpgradeApps = genappup_which_apps(Upgraded, AppUpApps), - - %% Generate appup files for upgraded apps - generate_appup_files(NewVerPath, OldVerPath, ModDeps, UpgradeApps), - - {ok, Config1}. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, 'generate-appups') -> - ?CONSOLE("Generate appup files.~n" - "~n" - "Valid command line options:~n" - " previous_release=path~n", - []). - -get_apps(Name, OldVerPath, NewVerPath) -> - OldApps = rebar_rel_utils:get_rel_apps(Name, OldVerPath), - ?DEBUG("Old Version Apps: ~p~n", [OldApps]), - - NewApps = rebar_rel_utils:get_rel_apps(Name, NewVerPath), - ?DEBUG("New Version Apps: ~p~n", [NewApps]), - - Added = app_list_diff(NewApps, OldApps), - ?DEBUG("Added: ~p~n", [Added]), - - Removed = app_list_diff(OldApps, NewApps), - ?DEBUG("Removed: ~p~n", [Removed]), - - PossiblyUpgraded = proplists:get_keys(NewApps), - - UpgradedApps = [upgraded_app(AppName, - proplists:get_value(AppName, OldApps), - proplists:get_value(AppName, NewApps)) - || AppName <- PossiblyUpgraded], - - Upgraded = lists:dropwhile(fun(Elem) -> - Elem == false - end, lists:sort(UpgradedApps)), - - ?DEBUG("Upgraded: ~p~n", [Upgraded]), - - {Added, Removed, Upgraded}. - -upgraded_app(AppName, OldAppVer, NewAppVer) when OldAppVer /= NewAppVer -> - {AppName, {OldAppVer, NewAppVer}}; -upgraded_app(_, _, _) -> - false. - -app_list_diff(List1, List2) -> - List3 = lists:umerge(lists:sort(proplists:get_keys(List1)), - lists:sort(proplists:get_keys(List2))), - List3 -- proplists:get_keys(List2). - -file_to_name(File) -> - filename:rootname(filename:basename(File)). - -genappup_which_apps(UpgradedApps, [First|Rest]) -> - List = proplists:delete(list_to_atom(First), UpgradedApps), - genappup_which_apps(List, Rest); -genappup_which_apps(Apps, []) -> - Apps. - -generate_appup_files(NewVerPath, OldVerPath, ModDeps, [{_App, {undefined, _}}|Rest]) -> - generate_appup_files(NewVerPath, OldVerPath, ModDeps, Rest); -generate_appup_files(NewVerPath, OldVerPath, ModDeps, [{App, {OldVer, NewVer}}|Rest]) -> - OldEbinDir = filename:join([OldVerPath, "lib", - atom_to_list(App) ++ "-" ++ OldVer, "ebin"]), - NewEbinDir = filename:join([NewVerPath, "lib", - atom_to_list(App) ++ "-" ++ NewVer, "ebin"]), - - {AddedFiles, DeletedFiles, ChangedFiles} = beam_lib:cmp_dirs(NewEbinDir, - OldEbinDir), - - ChangedNames = [list_to_atom(file_to_name(F)) || {F, _} <- ChangedFiles], - ModDeps1 = [{N, [M1 || M1 <- M, lists:member(M1, ChangedNames)]} - || {N, M} <- ModDeps], - - Added = [generate_instruction(added, File) || File <- AddedFiles], - Deleted = [generate_instruction(deleted, File) || File <- DeletedFiles], - Changed = [generate_instruction(changed, ModDeps1, File) - || File <- ChangedFiles], - - Inst = lists:append([Added, Deleted, Changed]), - - AppUpFile = filename:join([NewEbinDir, atom_to_list(App) ++ ".appup"]), - - ok = file:write_file(AppUpFile, - io_lib:fwrite(?APPUPFILEFORMAT, - [App, rebar_utils:now_str(), NewVer, - OldVer, Inst, OldVer])), - - ?CONSOLE("Generated appup for ~p~n", [App]), - generate_appup_files(NewVerPath, OldVerPath, ModDeps, Rest); -generate_appup_files(_, _, _, []) -> - ?CONSOLE("Appup generation complete~n", []). - -generate_instruction(added, File) -> - Name = list_to_atom(file_to_name(File)), - {add_module, Name}; -generate_instruction(deleted, File) -> - Name = list_to_atom(file_to_name(File)), - {delete_module, Name}. - -generate_instruction(changed, ModDeps, {File, _}) -> - {ok, {Name, List}} = beam_lib:chunks(File, [attributes, exports]), - Behavior = get_behavior(List), - CodeChange = is_code_change(List), - Deps = proplists:get_value(Name, ModDeps, []), - generate_instruction_advanced(Name, Behavior, CodeChange, Deps). - -generate_instruction_advanced(Name, undefined, undefined, Deps) -> - %% Not a behavior or code change, assume purely functional - {load_module, Name, Deps}; -generate_instruction_advanced(Name, [supervisor], _, _) -> - %% Supervisor - {update, Name, supervisor}; -generate_instruction_advanced(Name, _, code_change, Deps) -> - %% Includes code_change export - {update, Name, {advanced, []}, Deps}; -generate_instruction_advanced(Name, _, _, Deps) -> - %% Anything else - {load_module, Name, Deps}. - -get_behavior(List) -> - Attributes = proplists:get_value(attributes, List), - case proplists:get_value(behavior, Attributes) of - undefined -> proplists:get_value(behaviour, Attributes); - Else -> Else - end. - -is_code_change(List) -> - Exports = proplists:get_value(exports, List), - case proplists:is_defined(code_change, Exports) of - true -> - code_change; - false -> - undefined - end. diff --git a/src/rebar_asn1_compiler.erl b/src/rebar_asn1_compiler.erl deleted file mode 100644 index 25e3fd3..0000000 --- a/src/rebar_asn1_compiler.erl +++ /dev/null @@ -1,100 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2009, 2010 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_asn1_compiler). --author('ruslan@babayev.com'). - --export([compile/2, - clean/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - --spec compile(rebar_config:config(), file:filename()) -> 'ok'. -compile(Config, _AppFile) -> - rebar_base_compiler:run(Config, filelib:wildcard("asn1/*.asn1"), - "asn1", ".asn1", "src", ".erl", - fun compile_asn1/3). - --spec clean(rebar_config:config(), file:filename()) -> 'ok'. -clean(_Config, _AppFile) -> - GeneratedFiles = asn_generated_files("asn1", "src", "include"), - ok = rebar_file_utils:delete_each(GeneratedFiles), - ok. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - info_help("Build ASN.1 (*.asn1) sources"); -info(help, clean) -> - info_help("Delete ASN.1 (*.asn1) results"). - -info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " {asn1_opts, []} (see asn1ct:compile/2 documentation)~n", - [Description]). - --spec compile_asn1(file:filename(), file:filename(), - rebar_config:config()) -> ok. -compile_asn1(Source, Target, Config) -> - ok = filelib:ensure_dir(Target), - ok = filelib:ensure_dir(filename:join("include", "dummy.hrl")), - Opts = [{outdir, "src"}, noobj] ++ rebar_config:get(Config, asn1_opts, []), - case asn1ct:compile(Source, Opts) of - ok -> - Asn1 = filename:basename(Source, ".asn1"), - HrlFile = filename:join("src", Asn1 ++ ".hrl"), - case filelib:is_regular(HrlFile) of - true -> - ok = rebar_file_utils:mv(HrlFile, "include"); - false -> - ok - end; - {error, _Reason} -> - ?FAIL - end. - -asn_generated_files(AsnDir, SrcDir, IncDir) -> - lists:foldl( - fun(AsnFile, Acc) -> - Base = filename:rootname(filename:basename(AsnFile)), - [filename:join([IncDir, Base ++ ".hrl"])| - filelib:wildcard(filename:join([SrcDir, Base ++ ".*"]))] ++ Acc - end, - [], - filelib:wildcard(filename:join([AsnDir, "*.asn1"])) - ). diff --git a/src/rebar_config.erl b/src/rebar_config.erl index bdc3fb5..11756b7 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -26,7 +26,7 @@ %% ------------------------------------------------------------------- -module(rebar_config). --export([new/0, new/1, base_config/1, consult_file/1, +-export([new/0, new/1, new2/2, base_config/1, consult_file/1, get/3, get_local/3, get_list/3, get_all/2, set/3, @@ -34,7 +34,11 @@ is_recursive/1, save_env/3, get_env/2, reset_envs/1, set_skip_dir/2, is_skip_dir/2, reset_skip_dirs/1, - clean_config/2, + create_logic_providers/2, + providers/1, providers/2, add_provider/2, + add_dep/2, get_dep/2, deps/2, deps/1, deps_graph/1, deps_graph/2, deps_to_build/1, + goals/1, goals/2, + get_app/2, apps_to_build/1, apps_to_build/2, add_app/2, replace_app/3, set_xconf/3, get_xconf/2, get_xconf/3, erase_xconf/2]). -include("rebar.hrl"). @@ -49,9 +53,16 @@ -record(config, { dir :: file:filename(), opts = [] :: list(), + local_opts = [] :: list(), globals = new_globals() :: rebar_dict(), envs = new_env() :: rebar_dict(), %% cross-directory/-command config + goals = [], + providers = [], + apps_to_build = [], + deps_to_build = [], + deps = [], + deps_graph = undefined, skip_dirs = new_skip_dirs() :: rebar_dict(), xconf = new_xconf() :: rebar_dict() }). @@ -60,6 +71,7 @@ -opaque config() :: #config{}. -define(DEFAULT_NAME, "rebar.config"). +-define(LOCK_FILE, "rebar.lock"). %% =================================================================== %% Public API @@ -80,11 +92,44 @@ new(ConfigFile) when is_list(ConfigFile) -> Other -> ?ABORT("Failed to load ~s: ~p~n", [ConfigFile, Other]) end; -new(_ParentConfig=#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, - xconf=Xconf}) -> +new(_ParentConfig=#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, xconf=Xconf}) -> new(#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, xconf=Xconf}, ?DEFAULT_NAME). +new(ParentConfig, ConfName) -> + %% Load terms from rebar.config, if it exists + Dir = rebar_utils:get_cwd(), + new(ParentConfig, ConfName, Dir). + +new2(_ParentConfig=#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, xconf=Xconf}, Dir) -> + new(#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, xconf=Xconf}, + ?DEFAULT_NAME, Dir). + +new(ParentConfig, ConfName, Dir) -> + ConfigFile = filename:join([Dir, ConfName]), + Opts0 = ParentConfig#config.opts, + Opts = case consult_file(ConfigFile) of + {ok, Terms} -> + Terms; + {error, enoent} -> + []; + Other -> + ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other]) + end, + + Opts1 = case consult_file(?LOCK_FILE) of + {ok, [D]} -> + lists:keyreplace(deps, 1, Opts, {deps, D}); + _ -> + Opts + end, + + ProviderModules = [], + create_logic_providers(ProviderModules, ParentConfig#config{dir=Dir + ,local_opts=Opts1 + ,opts=Opts0}). + + get(Config, Key, Default) -> proplists:get_value(Key, Config#config.opts, Default). @@ -92,7 +137,7 @@ get_list(Config, Key, Default) -> get(Config, Key, Default). get_local(Config, Key, Default) -> - proplists:get_value(Key, local_opts(Config#config.opts, []), Default). + proplists:get_value(Key, Config#config.local_opts, Default). get_all(Config, Key) -> proplists:get_all_values(Key, Config#config.opts). @@ -121,6 +166,8 @@ get_global(Config, Key, Default) -> is_recursive(Config) -> get_xconf(Config, recursive, false). +consult_file(File) when is_binary(File) -> + consult_file(binary_to_list(File)); consult_file(File) -> case filename:extension(File) of ".script" -> @@ -183,38 +230,68 @@ erase_xconf(Config, Key) -> NewXconf = dict:erase(Key, Config#config.xconf), Config#config{xconf = NewXconf}. -%% TODO: reconsider after config inheritance removal/redesign -clean_config(Old, New) -> - New#config{opts=Old#config.opts}. +get_dep(#config{deps=Apps}, Name) -> + lists:keyfind(Name, 2, Apps). + +deps(#config{deps=Apps}) -> + Apps. + +deps(Config, Apps) -> + Config#config{deps=Apps}. + +deps_graph(#config{deps_graph=Graph}) -> + Graph. + +deps_graph(Config, Graph) -> + Config#config{deps_graph=Graph}. + +get_app(#config{apps_to_build=Apps}, Name) -> + lists:keyfind(Name, 2, Apps). + +apps_to_build(#config{apps_to_build=Apps}) -> + Apps. + +apps_to_build(Config, Apps) -> + Config#config{apps_to_build=Apps}. + +add_app(Config=#config{apps_to_build=Apps}, App) -> + Config#config{apps_to_build=[App | Apps]}. + +replace_app(Config=#config{apps_to_build=Apps}, Name, App) -> + Apps1 = lists:keydelete(Name, 2, Apps), + Config#config{apps_to_build=[App | Apps1]}. + +deps_to_build(#config{deps_to_build=Apps}) -> + Apps. + +add_dep(Config=#config{deps_to_build=Apps}, App) -> + Config#config{deps_to_build=[App | Apps]}. + +providers(#config{providers=Providers}) -> + Providers. + +providers(Config, NewProviders) -> + Config#config{providers=NewProviders}. + +goals(#config{goals=Goals}) -> + Goals. + +goals(Config, Goals) -> + Config#config{goals=Goals}. + +add_provider(Config=#config{providers=Providers}, Provider) -> + Config#config{providers=[Provider | Providers]}. + +create_logic_providers(ProviderModules, State0) -> + lists:foldl(fun(ProviderMod, Acc) -> + {ok, State1} = rebar_provider:new(ProviderMod, Acc), + State1 + end, State0, ProviderModules). %% =================================================================== %% Internal functions %% =================================================================== -new(ParentConfig, ConfName) -> - %% Load terms from rebar.config, if it exists - Dir = rebar_utils:get_cwd(), - ConfigFile = filename:join([Dir, ConfName]), - Opts0 = ParentConfig#config.opts, - Opts = case consult_file(ConfigFile) of - {ok, Terms} -> - %% Found a config file with some terms. We need to - %% be able to distinguish between local definitions - %% (i.e. from the file in the cwd) and inherited - %% definitions. To accomplish this, we use a marker - %% in the proplist (since order matters) between - %% the new and old defs. - Terms ++ [local] ++ - [Opt || Opt <- Opts0, Opt /= local]; - {error, enoent} -> - [local] ++ - [Opt || Opt <- Opts0, Opt /= local]; - Other -> - ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other]) - end, - - ParentConfig#config{dir = Dir, opts = Opts}. - consult_and_eval(File, Script) -> ?DEBUG("Evaluating config script ~p~n", [Script]), ConfigData = try_consult(File), @@ -240,13 +317,6 @@ bs(Vars) -> erl_eval:add_binding(K, V, Bs) end, erl_eval:new_bindings(), Vars). -local_opts([], Acc) -> - lists:reverse(Acc); -local_opts([local | _Rest], Acc) -> - lists:reverse(Acc); -local_opts([Item | Rest], Acc) -> - local_opts(Rest, [Item | Acc]). - new_globals() -> dict:new(). new_env() -> dict:new(). diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 4557bb8..55a96e9 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -35,431 +35,47 @@ %% =================================================================== help(ParentConfig, Commands) -> - %% get all core modules - {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules), - {ok, RawCoreModules} = application:get_env(rebar, modules), - AppDirModules = proplists:get_value(app_dir, RawCoreModules), - RelDirModules = proplists:get_value(rel_dir, RawCoreModules), - CoreModules = AnyDirModules ++ AppDirModules ++ RelDirModules, + {ok, AllProviders} = application:get_env(rebar, providers), - %% get plugin modules - Predirs = [], + %% get plugin providers Dir = rebar_utils:get_cwd(), - PredirsAssoc = remember_cwd_predirs(Dir, Predirs), - Config = maybe_load_local_config(Dir, ParentConfig), - {ok, PluginModules} = plugin_modules(Config, PredirsAssoc), - - AllModules = CoreModules ++ PluginModules, + _Config = maybe_load_local_config(Dir, ParentConfig), lists:foreach( fun(Cmd) -> ?CONSOLE("==> help ~p~n~n", [Cmd]), - CmdModules = select_modules(AllModules, Cmd, []), - Modules = select_modules(CmdModules, info, []), + CmdProviders = rebar_provider:get_target_provider(Cmd, AllProviders), + Providers = rebar_provider:get_target_provider(info, CmdProviders), lists:foreach(fun(M) -> ?CONSOLE("=== ~p:~p ===~n", [M, Cmd]), M:info(help, Cmd), ?CONSOLE("~n", []) - end, Modules) + end, Providers) end, Commands). -process_commands([], ParentConfig) -> - AbortTrapped = rebar_config:get_xconf(ParentConfig, abort_trapped, false), - case {get_operations(ParentConfig), AbortTrapped} of - {0, _} -> - %% None of the commands had any effect - ?FAIL; - {_, true} -> - %% An abort was previously trapped - ?FAIL; - _ -> - ok - end; -process_commands([Command | Rest], ParentConfig) -> - %% Reset skip dirs - ParentConfig1 = rebar_config:reset_skip_dirs(ParentConfig), - Operations = get_operations(ParentConfig1), - - ParentConfig4 = - try - %% Convert the code path so that all the entries are - %% absolute paths. If not, code:set_path() may choke on - %% invalid relative paths when trying to restore the code - %% path from inside a subdirectory. - true = rebar_utils:expand_code_path(), - {ParentConfig2, _DirSet} = process_dir(rebar_utils:get_cwd(), - Command, ParentConfig1, - sets:new()), - case get_operations(ParentConfig2) of - Operations -> - %% This command didn't do anything - ?CONSOLE("Command '~p' not understood or not applicable~n", - [Command]); - _ -> - ok - end, - %% TODO: reconsider after config inheritance removal/re-design - ParentConfig3 = rebar_config:clean_config(ParentConfig1, - ParentConfig2), - %% Wipe out vsn cache to avoid invalid hits when - %% dependencies are updated - rebar_config:set_xconf(ParentConfig3, vsn_cache, dict:new()) - catch - throw:rebar_abort -> - case rebar_config:get_xconf(ParentConfig1, keep_going, false) of - false -> - ?FAIL; - true -> - ?WARN("Continuing on after abort: ~p\n", [Rest]), - rebar_config:set_xconf(ParentConfig1, - abort_trapped, true) - end - end, - process_commands(Rest, ParentConfig4). - -process_dir(Dir, Command, ParentConfig, DirSet) -> - case filelib:is_dir(Dir) of - false -> - ?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]), - {ParentConfig, DirSet}; - true -> - WouldCd = would_cd_into_dir(Dir, Command, ParentConfig), - ok = file:set_cwd(Dir), - Config = maybe_load_local_config(Dir, ParentConfig), - - %% Save the current code path and then update it with - %% lib_dirs. Children inherit parents code path, but we also - %% want to ensure that we restore everything to pristine - %% condition after processing this child - CurrentCodePath = update_code_path(Config), - - %% Get the list of processing modules and check each one - %% against CWD to see if it's a fit -- if it is, use that - %% set of modules to process this dir. - {ok, AvailModuleSets} = application:get_env(rebar, modules), - ModuleSet = choose_module_set(AvailModuleSets, Dir), - skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, - ModuleSet, WouldCd) - end. - -would_cd_into_dir(Dir, Command, Config) -> - case would_cd_into_dir1(Dir, Command, Config) of - true -> - would_cd; - false -> - would_not_cd - end. - -would_cd_into_dir1(Dir, Command, Config) -> - rebar_utils:processing_base_dir(Config, Dir) orelse - rebar_config:is_recursive(Config) orelse - is_recursive_command(Command, Config) orelse - is_generate_in_rel_dir(Command, Dir). - -%% Check whether the command is part of the built-in (or extended via -%% rebar.config) list of default-recursive commands. -is_recursive_command(Command, Config) -> - {ok, AppCmds} = application:get_env(rebar, recursive_cmds), - ConfCmds = rebar_config:get_local(Config, recursive_cmds, []), - RecursiveCmds = AppCmds ++ ConfCmds, - lists:member(Command, RecursiveCmds). - -%% If the directory we're about to process contains -%% reltool.config[.script] and the command to be applied is -%% 'generate', then it's safe to process. We do this to retain the -%% behavior of specifying {sub_dirs, ["rel"]} and have "rebar generate" -%% pick up rel/reltool.config[.script]. Without this workaround you'd -%% have to run "rebar -r generate" (which you don't want to do if you -%% have deps or other sub_dirs) or "cd rel && rebar generate". -is_generate_in_rel_dir(generate, Dir) -> - case rebar_rel_utils:is_rel_dir(Dir) of - {true, _} -> - true; - false -> - false - end; -is_generate_in_rel_dir(_, _) -> - false. - -skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, - {[], undefined}=ModuleSet, WouldCd) -> - process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, ModuleSet, - WouldCd); -skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, - {_, File}=ModuleSet, WouldCd) -> - case lists:suffix(".app.src", File) - orelse lists:suffix(".app", File) of - true -> - %% .app or .app.src file, check if is_skipped_app - skip_or_process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, - ModuleSet, WouldCd, File); - false -> - %% not an app dir, no need to consider apps=/skip_apps= - process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, - ModuleSet, WouldCd) - end. - -skip_or_process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, ModuleSet, - WouldCd, AppFile) -> - case rebar_app_utils:is_skipped_app(Config, AppFile) of - {Config1, {true, _SkippedApp}} when Command == 'update-deps' -> - %% update-deps does its own app skipping. Unfortunately there's no - %% way to signal this to rebar_core, so we have to explicitly do it - %% here... Otherwise if you use app=, it'll skip the toplevel - %% directory and nothing will be updated. - process_dir1(Dir, Command, Config1, DirSet, CurrentCodePath, - ModuleSet, WouldCd); - {Config1, {true, SkippedApp}} -> - ?DEBUG("Skipping app: ~p~n", [SkippedApp]), - {increment_operations(Config1), DirSet}; - {Config1, false} -> - process_dir1(Dir, Command, Config1, DirSet, CurrentCodePath, - ModuleSet, WouldCd) - end. - -process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, - {DirModules, File}, WouldCd) -> - Config0 = rebar_config:set_xconf(Config, current_command, Command), - - %% Get the list of modules for "any dir". This is a catch-all list - %% of modules that are processed in addition to modules associated - %% with this directory type. These any_dir modules are processed - %% FIRST. - {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules), - - Modules = AnyDirModules ++ DirModules, - - %% Invoke 'preprocess' on the modules -- this yields a list of other - %% directories that should be processed _before_ the current one. - {Config1, Predirs} = acc_modules(Modules, preprocess, Config0, File), - - %% Remember associated pre-dirs (used for plugin lookup) - PredirsAssoc = remember_cwd_predirs(Dir, Predirs), - - %% Get the list of plug-in modules from rebar.config. These - %% modules may participate in preprocess and postprocess. - {ok, PluginModules} = plugin_modules(Config1, PredirsAssoc), - AllModules = Modules ++ PluginModules, - - {Config2, PluginPredirs} = acc_modules(PluginModules, preprocess, Config1, - File), - - AllPredirs = Predirs ++ PluginPredirs, - - ?DEBUG("Predirs: ~p\n", [AllPredirs]), - {Config3, DirSet2} = process_each(AllPredirs, Command, Config2, DirSet, - File), - - %% Make sure the CWD is reset properly; processing the dirs may have - %% caused it to change - ok = file:set_cwd(Dir), - - %% Maybe apply command to Dir - Config4 = maybe_execute(Dir, Command, Config3, Modules, PluginModules, - AllModules, File, WouldCd), - - %% Mark the current directory as processed - DirSet3 = sets:add_element(Dir, DirSet2), - - %% Invoke 'postprocess' on the modules. This yields a list of other - %% directories that should be processed _after_ the current one. - {Config5, Postdirs} = acc_modules(AllModules, postprocess, Config4, File), - ?DEBUG("Postdirs: ~p\n", [Postdirs]), - Res = process_each(Postdirs, Command, Config5, DirSet3, File), - - %% Make sure the CWD is reset properly; processing the dirs may have - %% caused it to change - ok = file:set_cwd(Dir), - - %% Once we're all done processing, reset the code path to whatever - %% the parent initialized it to - restore_code_path(CurrentCodePath), - - %% Return the updated {config, dirset} as result - Res. - -maybe_execute(Dir, Command, Config, Modules, PluginModules, AllModules, File, - would_cd) -> - %% Check that this directory is not on the skip list - case rebar_config:is_skip_dir(Config, Dir) of - true -> - %% Do not execute the command on the directory, as some - %% module has requested a skip on it. - ?INFO("Skipping ~s in ~s\n", [Command, Dir]), - Config; - - false -> - %% Check for and get command specific environments - {Config1, Env} = setup_envs(Config, Modules), - - %% Execute any before_command plugins on this directory - Config2 = execute_pre(Command, PluginModules, Config1, File, Env), - - %% Execute the current command on this directory - Config3 = execute(Command, AllModules, Config2, File, Env), - - %% Execute any after_command plugins on this directory - execute_post(Command, PluginModules, Config3, File, Env) - end; -maybe_execute(_Dir, _Command, Config, _Modules, _PluginModules, _AllModules, - _File, would_not_cd) -> - Config. - -remember_cwd_predirs(Cwd, Predirs) -> - Store = fun(Dir, Dict) -> - case dict:find(Dir, Dict) of - error -> - ?DEBUG("Associate sub_dir ~s with ~s~n", - [Dir, Cwd]), - dict:store(Dir, Cwd, Dict); - {ok, Existing} -> - ?ABORT("Internal consistency assertion failed.~n" - "sub_dir ~s already associated with ~s.~n" - "Duplicate sub_dirs or deps entries?", - [Dir, Existing]) - end - end, - lists:foldl(Store, dict:new(), Predirs). - -maybe_load_local_config(Dir, ParentConfig) -> - %% We need to ensure we don't overwrite custom - %% config when we are dealing with base_dir. - case rebar_utils:processing_base_dir(ParentConfig, Dir) of - true -> - ParentConfig; - false -> - rebar_config:new(ParentConfig) - end. - -%% -%% Given a list of directories and a set of previously processed directories, -%% process each one we haven't seen yet -%% -process_each([], _Command, Config, DirSet, _File) -> - %% reset cached (setup_env) envs - Config1 = rebar_config:reset_envs(Config), - {Config1, DirSet}; -process_each([Dir | Rest], Command, Config, DirSet, File) -> - case sets:is_element(Dir, DirSet) of - true -> - ?DEBUG("Skipping ~s; already processed!\n", [Dir]), - process_each(Rest, Command, Config, DirSet, File); - false -> - {Config1, DirSet2} = process_dir(Dir, Command, Config, DirSet), - Config2 = rebar_config:clean_config(Config, Config1), - %% reset cached (setup_env) envs - Config3 = rebar_config:reset_envs(Config2), - process_each(Rest, Command, Config3, DirSet2, File) - end. - -%% -%% Given a list of module sets from rebar.app and a directory, find -%% the appropriate subset of modules for this directory -%% -choose_module_set([], _Dir) -> - {[], undefined}; -choose_module_set([{Type, Modules} | Rest], Dir) -> - case is_dir_type(Type, Dir) of - {true, File} -> - {Modules, File}; - false -> - choose_module_set(Rest, Dir) - end. - -is_dir_type(app_dir, Dir) -> - rebar_app_utils:is_app_dir(Dir); -is_dir_type(rel_dir, Dir) -> - rebar_rel_utils:is_rel_dir(Dir); -is_dir_type(_, _) -> - false. - -execute_pre(Command, Modules, Config, ModuleFile, Env) -> - execute_plugin_hook("pre_", Command, Modules, - Config, ModuleFile, Env). - -execute_post(Command, Modules, Config, ModuleFile, Env) -> - execute_plugin_hook("post_", Command, Modules, - Config, ModuleFile, Env). - -execute_plugin_hook(Hook, Command, Modules, Config, ModuleFile, Env) -> - HookFunction = list_to_atom(Hook ++ atom_to_list(Command)), - execute(HookFunction, hook, Modules, Config, ModuleFile, Env). - -%% -%% Execute a command across all applicable modules -%% -execute(Command, Modules, Config, ModuleFile, Env) -> - execute(Command, not_a_hook, Modules, Config, ModuleFile, Env). - -execute(Command, Type, Modules, Config, ModuleFile, Env) -> - case select_modules(Modules, Command, []) of - [] -> - case Type of - hook -> - ok; - not_a_hook -> - ?WARN("'~p' command does not apply to directory ~s\n", - [Command, rebar_utils:get_cwd()]) - end, - Config; - - TargetModules -> - %% Provide some info on where we are - Dir = rebar_utils:get_cwd(), - ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]), - - Config1 = increment_operations(Config), - - %% Run the available modules - apply_hooks(pre_hooks, Config1, Command, Env), - case catch(run_modules(TargetModules, Command, - Config1, ModuleFile)) of - {ok, NewConfig} -> - apply_hooks(post_hooks, NewConfig, Command, Env), - NewConfig; - {error, failed} -> - ?FAIL; - {Module, {error, _} = Other} -> - ?ABORT("~p failed while processing ~s in module ~s: ~s\n", - [Command, Dir, Module, - io_lib:print(Other, 1, 80, -1)]); - Other -> - ?ABORT("~p failed while processing ~s: ~s\n", - [Command, Dir, io_lib:print(Other, 1, 80, -1)]) - end - end. - -%% Increment the count of operations, since some module -%% responds to this command -increment_operations(Config) -> - Operations = get_operations(Config), - rebar_config:set_xconf(Config, operations, Operations + 1). - -get_operations(Config) -> - rebar_config:get_xconf(Config, operations). - -update_code_path(Config) -> - case rebar_config:get_local(Config, lib_dirs, []) of - [] -> - no_change; - Paths -> - LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []), - ok = code:add_pathsa(LibPaths), - %% track just the paths we added, so we can remove them without - %% removing other paths added by this dep - {added, LibPaths} - end. - -restore_code_path(no_change) -> - ok; -restore_code_path({added, Paths}) -> - %% Verify that all of the paths still exist -- some dynamically - %% added paths can get blown away during clean. - _ = [code:del_path(F) || F <- Paths, erl_prim_loader_is_file(F)], - ok. - -erl_prim_loader_is_file(File) -> - erl_prim_loader:read_file_info(File) =/= error. +process_commands(Commands, ParentConfig) -> + true = rebar_utils:expand_code_path(), + LibDirs = rebar_config:get_local(ParentConfig, lib_dirs, ["apps", "libs", "."]), + DepsDir = rebar_deps:get_deps_dir(ParentConfig), + _UpdatedCodePaths = update_code_path([DepsDir | LibDirs]), + + ParentConfig2 = rebar_app_discover:do(ParentConfig, LibDirs), + TargetProviders = rebar_provider:get_target_providers(Commands, ParentConfig2), + ParentConfig3 = + lists:foldl(fun(TargetProvider, Conf) -> + Provider = rebar_provider:get_provider(TargetProvider, rebar_config:providers(Conf)), + {ok, Conf1} = rebar_provider:do(Provider, Conf), + Conf1 + end, ParentConfig2, TargetProviders). + +update_code_path([]) -> + no_change; +update_code_path(Paths) -> + LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []), + ok = code:add_pathsa(LibPaths), + %% track just the paths we added, so we can remove them without + %% removing other paths added by this dep + {added, LibPaths}. expand_lib_dirs([], _Root, Acc) -> Acc; @@ -471,187 +87,12 @@ expand_lib_dirs([Dir | Rest], Root, Acc) -> end, expand_lib_dirs(Rest, Root, Acc ++ FqApps). - - -select_modules([], _Command, Acc) -> - lists:reverse(Acc); -select_modules([Module | Rest], Command, Acc) -> - {module, Module} = code:ensure_loaded(Module), - case erlang:function_exported(Module, Command, 2) of - true -> - select_modules(Rest, Command, [Module | Acc]); - false -> - select_modules(Rest, Command, Acc) - end. - -run_modules([], _Command, Config, _File) -> - {ok, Config}; -run_modules([Module | Rest], Command, Config, File) -> - case Module:Command(Config, File) of - ok -> - run_modules(Rest, Command, Config, File); - {ok, NewConfig} -> - run_modules(Rest, Command, NewConfig, File); - {error, _} = Error -> - {Module, Error} - end. - -apply_hooks(Mode, Config, Command, Env0) -> - Hooks = rebar_config:get_local(Config, Mode, []), - Env = rebar_utils:patch_env(Config, Env0), - lists:foreach(fun apply_hook/1, - [{Env, Hook} || Hook <- Hooks, - element(1, Hook) =:= Command orelse - element(2, Hook) =:= Command]). - -apply_hook({Env, {Arch, Command, Hook}}) -> - case rebar_utils:is_arch(Arch) of - true -> - apply_hook({Env, {Command, Hook}}); - false -> - ok - end; -apply_hook({Env, {Command, Hook}}) -> - Msg = lists:flatten(io_lib:format("Command [~p] failed!~n", [Command])), - rebar_utils:sh(Hook, [{env, Env}, {abort_on_error, Msg}]). - -setup_envs(Config, Modules) -> - lists:foldl(fun(M, {C,E}=T) -> - case erlang:function_exported(M, setup_env, 1) of - true -> - Env = M:setup_env(C), - C1 = rebar_config:save_env(C, M, Env), - {C1, E++Env}; - false -> - T - end - end, {Config, []}, Modules). - -acc_modules(Modules, Command, Config, File) -> - acc_modules(select_modules(Modules, Command, []), - Command, Config, File, []). - -acc_modules([], _Command, Config, _File, Acc) -> - {Config, Acc}; -acc_modules([Module | Rest], Command, Config, File, Acc) -> - {Config1, Dirs1} = case Module:Command(Config, File) of - {ok, Dirs} -> - {Config, Dirs}; - {ok, NewConfig, Dirs} -> - {NewConfig, Dirs} - end, - acc_modules(Rest, Command, Config1, File, Acc ++ Dirs1). - -%% -%% Return a flat list of rebar plugin modules. -%% -plugin_modules(Config, PredirsAssoc) -> - Modules = lists:flatten(rebar_config:get_all(Config, plugins)), - ?DEBUG("Plugins requested while processing ~s: ~p~n", - [rebar_utils:get_cwd(), Modules]), - plugin_modules(Config, PredirsAssoc, ulist(Modules)). - -ulist(L) -> - ulist(L, []). - -ulist([], Acc) -> - lists:reverse(Acc); -ulist([H | T], Acc) -> - case lists:member(H, Acc) of - true -> - ulist(T, Acc); - false -> - ulist(T, [H | Acc]) - end. - -plugin_modules(_Config, _PredirsAssoc, []) -> - {ok, []}; -plugin_modules(Config, PredirsAssoc, Modules) -> - FoundModules = [M || M <- Modules, code:which(M) =/= non_existing], - plugin_modules(Config, PredirsAssoc, FoundModules, Modules -- FoundModules). - -plugin_modules(_Config, _PredirsAssoc, FoundModules, []) -> - {ok, FoundModules}; -plugin_modules(Config, PredirsAssoc, FoundModules, MissingModules) -> - {Loaded, NotLoaded} = load_plugin_modules(Config, PredirsAssoc, - MissingModules), - AllViablePlugins = FoundModules ++ Loaded, - case NotLoaded =/= [] of +maybe_load_local_config(Dir, ParentConfig) -> + %% We need to ensure we don't overwrite custom + %% config when we are dealing with base_dir. + case rebar_utils:processing_base_dir(ParentConfig, Dir) of true -> - %% NB: we continue to ignore this situation, as did the - %% original code - ?WARN("Missing plugins: ~p\n", [NotLoaded]); - false -> - ?DEBUG("Loaded plugins: ~p~n", [AllViablePlugins]), - ok - end, - {ok, AllViablePlugins}. - -load_plugin_modules(Config, PredirsAssoc, Modules) -> - Cwd = rebar_utils:get_cwd(), - PluginDirs = get_all_plugin_dirs(Config, Cwd, PredirsAssoc), - ?DEBUG("Plugin dirs for ~s:~n~p~n", [Cwd, PluginDirs]), - - %% Find relevant sources in base_dir and plugin_dir - RE = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"), - %% If a plugin is found both in base_dir and plugin_dir, the clash - %% will provoke an error and we'll abort. - Sources = [rebar_utils:find_files(PD, RE, false) || PD <- PluginDirs], - - %% Compile and load plugins - Loaded = [load_plugin(Src) || Src <- lists:append(Sources)], - FilterMissing = is_missing_plugin(Loaded), - NotLoaded = [V || V <- Modules, FilterMissing(V)], - {Loaded, NotLoaded}. - -get_all_plugin_dirs(Config, Cwd, PredirsAssoc) -> - [rebar_utils:get_cwd()] - ++ get_plugin_dir(Config, Cwd) - ++ get_base_plugin_dirs(Cwd, PredirsAssoc). - -get_plugin_dir(Config, Cwd) -> - case rebar_config:get_local(Config, plugin_dir, undefined) of - undefined -> - %% Plugin can be in the project's "plugins" folder - [filename:join(Cwd, "plugins")]; - Dir -> - [Dir] - end. - -%% We also want to include this case: -%% Plugin can be in "plugins" directory of the plugin base directory. -%% For example, Cwd depends on Plugin, and deps/Plugin/plugins/Plugin.erl -%% is the plugin. -get_base_plugin_dirs(Cwd, PredirsAssoc) -> - [filename:join(Dir, "plugins") || - Dir <- get_plugin_base_dirs(Cwd, PredirsAssoc)]. - -%% @doc PredirsAssoc is a dictionary of plugindir -> 'parent' pairs. -%% 'parent' in this case depends on plugin; therefore we have to give -%% all plugins that Cwd ('parent' in this case) depends on. -get_plugin_base_dirs(Cwd, PredirsAssoc) -> - [PluginDir || {PluginDir, Master} <- dict:to_list(PredirsAssoc), - Master =:= Cwd]. - -is_missing_plugin(Loaded) -> - fun(Mod) -> not lists:member(Mod, Loaded) end. - -load_plugin(Src) -> - case compile:file(Src, [binary, return_errors]) of - {ok, Mod, Bin} -> - load_plugin_module(Mod, Bin, Src); - {error, Errors, _Warnings} -> - ?ABORT("Plugin ~s contains compilation errors: ~p~n", - [Src, Errors]) - end. - -load_plugin_module(Mod, Bin, Src) -> - case code:is_loaded(Mod) of - {file, Loaded} -> - ?ABORT("Plugin ~p clashes with previously loaded module ~p~n", - [Mod, Loaded]); + ParentConfig; false -> - ?INFO("Loading plugin ~p from ~s~n", [Mod, Src]), - {module, Mod} = code:load_binary(Mod, Src, Bin), - Mod + rebar_config:new(ParentConfig) end. diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index bd94921..d9d0399 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -26,113 +26,109 @@ %% ------------------------------------------------------------------- -module(rebar_deps). +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + -include("rebar.hrl"). --export([preprocess/2, - postprocess/2, - compile/2, - setup_env/1, - 'check-deps'/2, - 'get-deps'/2, - 'update-deps'/2, - 'delete-deps'/2, - 'list-deps'/2]). +-export([setup_env/1]). %% for internal use only -export([info/2]). -export([get_deps_dir/1]). --record(dep, { dir, - app, - vsn_regex, - source, - is_raw }). %% is_raw = true means non-Erlang/OTP dependency +-define(PROVIDER, deps). +-define(DEPS, []). %% =================================================================== %% Public API %% =================================================================== -preprocess(Config, _) -> - %% Side effect to set deps_dir globally for all dependencies from - %% top level down. Means the root deps_dir is honoured or the default - %% used globally since it will be set on the first time through here - Config1 = set_shared_deps_dir(Config, get_shared_deps_dir(Config, [])), - - %% Get the list of deps for the current working directory and identify those - %% deps that are available/present. - Deps = rebar_config:get_local(Config1, deps, []), - {Config2, {AvailableDeps, MissingDeps}} = find_deps(Config1, find, Deps), +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = deps, + bare = false, + deps = ?DEPS, + example = "rebar deps", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + Deps = rebar_config:get_local(Config, deps, []), + Goals = lists:map(fun({Name, "", _}) -> + Name; + ({Name, ".*", _}) -> + Name; + ({Name, Vsn, _}) -> + {Name, Vsn} + end, Deps), - ?DEBUG("Available deps: ~p\n", [AvailableDeps]), - ?DEBUG("Missing deps : ~p\n", [MissingDeps]), + {Config1, Deps1} = update_deps(Config, Deps), + Config2 = rebar_config:deps(Config1, Deps1), - %% Add available deps to code path - Config3 = update_deps_code_path(Config2, AvailableDeps), + {ok, rebar_config:goals(Config2, Goals)}. - %% Filtering out 'raw' dependencies so that no commands other than - %% deps-related can be executed on their directories. - NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw], +update_deps(Config, Deps) -> + DepsDir = get_deps_dir(Config), - case rebar_config:get_xconf(Config, current_command, undefined) of - 'update-deps' -> - %% Skip ALL of the dep folders, we do this because we don't want - %% any other calls to preprocess() for update-deps beyond the - %% toplevel directory. They aren't actually harmful, but they slow - %% things down unnecessarily. - NewConfig = lists:foldl( - fun(D, Acc) -> - rebar_config:set_skip_dir(Acc, D#dep.dir) - end, - Config3, - collect_deps(rebar_utils:get_cwd(), Config3)), - %% Return the empty list, as we don't want anything processed before - %% us. - {ok, NewConfig, []}; - _ -> - %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core - %% so that the current command doesn't run on the dep dir. - %% However, pre/postprocess WILL run (and we want it to) for - %% transitivity purposes. - %% - %% Also, if skip_deps=comma,separated,app,list, then only the given - %% dependencies are skipped. - NewConfig = - case rebar_config:get_global(Config3, skip_deps, false) of - "true" -> - lists:foldl( - fun(#dep{dir = Dir}, C) -> - rebar_config:set_skip_dir(C, Dir) - end, Config3, AvailableDeps); - Apps when is_list(Apps) -> - SkipApps = [list_to_atom(App) || - App <- string:tokens(Apps, ",")], - lists:foldl( - fun(#dep{dir = Dir, app = App}, C) -> - case lists:member(App, SkipApps) of - true -> rebar_config:set_skip_dir(C, Dir); - false -> C - end - end, Config3, AvailableDeps); - _ -> - Config3 - end, + %% Find available apps to fulfill dependencies + FoundApps = rebar_app_discover:find_apps([DepsDir]), - %% Return all the available dep directories for process - {ok, NewConfig, dep_dirs(NonRawAvailableDeps)} - end. + %% Resolve deps and their dependencies + Deps1 = handle_deps(Deps, FoundApps), -postprocess(Config, _) -> - case rebar_config:get_xconf(Config, ?MODULE, undefined) of - undefined -> - {ok, []}; - Dirs -> - NewConfig = rebar_config:erase_xconf(Config, ?MODULE), - {ok, NewConfig, Dirs} + case download_missing_deps(Config, DepsDir, FoundApps, Deps1) of + {Config1, []} -> + {Config1, Deps1}; + {Config1, _} -> + update_deps(Config1, Deps1) end. -compile(Config, _) -> - {Config1, _AvailDeps} = do_check_deps(Config), - {ok, Config1}. +handle_deps(Deps, Found) -> + NewDeps = + lists:foldl(fun(X, DepsAcc) -> + C = rebar_config:new2(rebar_config:new(), rebar_app_info:dir(X)), + LocalDeps = rebar_config:get_local(C, deps, []), + [LocalDeps | DepsAcc] + end, [], Found), + NewDeps1 = lists:flatten(NewDeps), + + %% Weed out duplicates + lists:umerge(fun(A, B) -> + element(1, A) =:= element(1, B) + end, lists:usort(Deps), lists:usort(NewDeps1)). + +download_missing_deps(Config, DepsDir, Found, Deps) -> + Apps = rebar_config:apps_to_build(Config), + Missing = lists:filter(fun(X) -> + not lists:any(fun(F) -> + element(1, X) =:= element(2, F) + end, Found++Apps) + end, Deps), + ec_plists:foreach(fun(X) -> + TargetDir = get_deps_dir(DepsDir, element(1, X)), + case filelib:is_dir(TargetDir) of + true -> + ok; + false -> + rebar_fetch:download_source(TargetDir, element(3, X)) + end + end, Missing), + + Config1 = lists:foldl(fun(X, ConfigAcc) -> + TargetDir = get_deps_dir(DepsDir, element(1, X)), + [AppSrc] = rebar_app_discover:find_apps([TargetDir]), + rebar_config:add_dep(ConfigAcc, AppSrc) + end, Config, Missing), + + {Config1, Missing}. %% set REBAR_DEPS_DIR and ERL_LIBS environment variables setup_env(Config) -> @@ -152,99 +148,77 @@ setup_env(Config) -> end, [{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS]. -%% common function used by 'check-deps' and 'compile' -do_check_deps(Config) -> - %% Get the list of immediate (i.e. non-transitive) deps that are missing - Deps = rebar_config:get_local(Config, deps, []), - case find_deps(Config, find, Deps) of - {Config1, {AvailDeps, []}} -> - %% No missing deps - {Config1, AvailDeps}; - {_Config1, {_, MissingDeps}} -> - lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) -> - ?CONSOLE("Dependency not available: " - "~p-~s (~p)\n", [App, Vsn, Src]) - end, MissingDeps), - ?FAIL - end. - -'check-deps'(Config, _) -> - {Config1, AvailDeps} = do_check_deps(Config), - {ok, save_dep_dirs(Config1, AvailDeps)}. - -'get-deps'(Config, _) -> - %% Determine what deps are available and missing - Deps = rebar_config:get_local(Config, deps, []), - {Config1, {_AvailableDeps, MissingDeps}} = find_deps(Config, find, Deps), - MissingDeps1 = [D || D <- MissingDeps, D#dep.source =/= undefined], - %% For each missing dep with a specified source, try to pull it. - {Config2, PulledDeps} = - lists:foldl(fun(D, {C, PulledDeps0}) -> - {C1, D1} = use_source(C, D), - {C1, [D1 | PulledDeps0]} - end, {Config1, []}, MissingDeps1), - - %% Add each pulled dep to our list of dirs for post-processing. This yields - %% the necessary transitivity of the deps - {ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}. - -'update-deps'(Config, _) -> - Config1 = rebar_config:set_xconf(Config, depowner, dict:new()), - {Config2, UpdatedDeps} = update_deps_int(Config1, []), - DepOwners = rebar_config:get_xconf(Config2, depowner, dict:new()), +get_deps_dir(Config) -> + BaseDir = rebar_utils:base_dir(Config), + get_deps_dir(BaseDir, "deps"). - %% check for conflicting deps - _ = [?ERROR("Conflicting dependencies for ~p: ~p~n", - [K, [{"From: " ++ string:join(dict:fetch(D, DepOwners), ", "), - {D#dep.vsn_regex, D#dep.source}} || D <- V]]) - || {K, V} <- dict:to_list( - lists:foldl( - fun(Dep, Acc) -> - dict:append(Dep#dep.app, Dep, Acc) - end, dict:new(), UpdatedDeps)), - length(V) > 1], +%% =================================================================== +%% Internal functions +%% =================================================================== - %% Add each updated dep to our list of dirs for post-processing. This yields - %% the necessary transitivity of the deps - {ok, save_dep_dirs(Config, UpdatedDeps)}. +get_deps_dir(DepsDir, App) -> + filename:join(DepsDir, App). + +-spec gather_application_info(file:name(), file:filename()) -> + {ok, rebar_app_info:t()} | + {warning, Reason::term()} | + {error, Reason::term()}. +gather_application_info(EbinDir, File) -> + AppDir = filename:dirname(EbinDir), + case file:consult(File) of + {ok, [{application, AppName, AppDetail}]} -> + validate_application_info(EbinDir, File, AppName, AppDetail); + {error, Reason} -> + {warning, {unable_to_load_app, AppDir, Reason}}; + _ -> + {warning, {invalid_app_file, File}} + end. -'delete-deps'(Config, _) -> - %% Delete all the available deps in our deps/ directory, if any - {true, DepsDir} = get_deps_dir(Config), - Deps = rebar_config:get_local(Config, deps, []), - {Config1, {AvailableDeps, _}} = find_deps(Config, find, Deps), - _ = [delete_dep(D) - || D <- AvailableDeps, - lists:prefix(DepsDir, D#dep.dir)], - {ok, Config1}. +-spec validate_application_info(file:name(), + file:name(), + atom(), + proplists:proplist()) -> + {ok, list()} | + {warning, Reason::term()} | + {error, Reason::term()}. +validate_application_info(EbinDir, AppFile, AppName, AppDetail) -> + AppDir = filename:dirname(EbinDir), + case get_modules_list(AppFile, AppDetail) of + {ok, List} -> + has_all_beams(EbinDir, List); + Error -> + Error + end. -'list-deps'(Config, _) -> - Deps = rebar_config:get_local(Config, deps, []), - case find_deps(Config, find, Deps) of - {Config1, {AvailDeps, []}} -> - lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps), - {ok, save_dep_dirs(Config1, AvailDeps)}; - {_, MissingDeps} -> - ?ABORT("Missing dependencies: ~p\n", [MissingDeps]) +-spec get_modules_list(file:name(), proplists:proplist()) -> + {ok, list()} | + {warning, Reason::term()} | + {error, Reason::term()}. +get_modules_list(AppFile, AppDetail) -> + case proplists:get_value(modules, AppDetail) of + undefined -> + {warning, {invalid_app_file, AppFile}}; + ModulesList -> + {ok, ModulesList} end. -%% =================================================================== -%% Internal functions -%% =================================================================== +-spec has_all_beams(file:name(), list()) -> + ok | {error, Reason::term()}. +has_all_beams(EbinDir, [Module | ModuleList]) -> + BeamFile = filename:join([EbinDir, + list_to_binary(atom_to_list(Module) ++ ".beam")]), + case filelib:is_file(BeamFile) of + true -> + has_all_beams(EbinDir, ModuleList); + false -> + {warning, {missing_beam_file, Module, BeamFile}} + end; +has_all_beams(_, []) -> + ok. -info(help, compile) -> - info_help("Display to be fetched dependencies"); -info(help, 'check-deps') -> - info_help("Display to be fetched dependencies"); -info(help, 'get-deps') -> - info_help("Fetch dependencies"); -info(help, 'update-deps') -> - info_help("Update fetched dependencies"); -info(help, 'delete-deps') -> - info_help("Delete fetched dependencies"); -info(help, 'list-deps') -> - info_help("List dependencies"). +info(help, 'deps') -> + info_help("Display dependencies"). info_help(Description) -> ?CONSOLE( @@ -281,540 +255,3 @@ info_help(Description) -> {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} ]). - -%% Added because of trans deps, -%% need all deps in same dir and should be the one set by the root rebar.config -%% In case one is given globally, it has higher priority -%% Sets a default if root config has no deps_dir set -set_shared_deps_dir(Config, []) -> - LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"), - GlobalDepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir), - DepsDir = case os:getenv("REBAR_DEPS_DIR") of - false -> - GlobalDepsDir; - Dir -> - Dir - end, - rebar_config:set_xconf(Config, deps_dir, DepsDir); -set_shared_deps_dir(Config, _DepsDir) -> - Config. - -get_shared_deps_dir(Config, Default) -> - rebar_config:get_xconf(Config, deps_dir, Default). - -get_deps_dir(Config) -> - get_deps_dir(Config, ""). - -get_deps_dir(Config, App) -> - BaseDir = rebar_utils:base_dir(Config), - DepsDir = get_shared_deps_dir(Config, "deps"), - {true, filename:join([BaseDir, DepsDir, App])}. - -dep_dirs(Deps) -> - [D#dep.dir || D <- Deps]. - -save_dep_dirs(Config, Deps) -> - rebar_config:set_xconf(Config, ?MODULE, dep_dirs(Deps)). - -get_lib_dir(App) -> - %% Find App amongst the reachable lib directories - %% Returns either the found path or a tagged tuple with a boolean - %% to match get_deps_dir's return type - case code:lib_dir(App) of - {error, bad_name} -> {false, bad_name}; - Path -> {true, Path} - end. - -update_deps_code_path(Config, []) -> - Config; -update_deps_code_path(Config, [Dep | Rest]) -> - Config2 = - case is_app_available(Config, Dep#dep.app, - Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of - {Config1, {true, _}} -> - Dir = filename:join(Dep#dep.dir, "ebin"), - ok = filelib:ensure_dir(filename:join(Dir, "dummy")), - ?DEBUG("Adding ~s to code path~n", [Dir]), - true = code:add_patha(Dir), - Config1; - {Config1, {false, _}} -> - Config1 - end, - update_deps_code_path(Config2, Rest). - -find_deps(Config, find=Mode, Deps) -> - find_deps(Config, Mode, Deps, {[], []}); -find_deps(Config, read=Mode, Deps) -> - find_deps(Config, Mode, Deps, []). - -find_deps(Config, find, [], {Avail, Missing}) -> - {Config, {lists:reverse(Avail), lists:reverse(Missing)}}; -find_deps(Config, read, [], Deps) -> - {Config, lists:reverse(Deps)}; -find_deps(Config, Mode, [App | Rest], Acc) when is_atom(App) -> - find_deps(Config, Mode, [{App, ".*", undefined} | Rest], Acc); -find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) -> - find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc); -find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) -> - find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc); -find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc) - when is_list(Opts) -> - Dep = #dep { app = App, - vsn_regex = VsnRegex, - source = Source, - %% dependency is considered raw (i.e. non-Erlang/OTP) when - %% 'raw' option is present - is_raw = proplists:get_value(raw, Opts, false) }, - {Config1, {Availability, FoundDir}} = find_dep(Config, Dep), - find_deps(Config1, Mode, Rest, - acc_deps(Mode, Availability, Dep, FoundDir, Acc)); -find_deps(_Config, _Mode, [Other | _Rest], _Acc) -> - ?ABORT("Invalid dependency specification ~p in ~s\n", - [Other, rebar_utils:get_cwd()]). - -find_dep(Config, Dep) -> - %% Find a dep based on its source, - %% e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"} - %% Deps with a source must be found (or fetched) locally. - %% Those without a source may be satisfied from lib dir (get_lib_dir). - find_dep(Config, Dep, Dep#dep.source). - -find_dep(Config, Dep, undefined) -> - %% 'source' is undefined. If Dep is not satisfied locally, - %% go ahead and find it amongst the lib_dir's. - case find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)) of - {_Config1, {avail, _Dir}} = Avail -> - Avail; - {Config1, {missing, _}} -> - find_dep_in_dir(Config1, Dep, get_lib_dir(Dep#dep.app)) - end; -find_dep(Config, Dep, _Source) -> - %% _Source is defined. Regardless of what it is, we must find it - %% locally satisfied or fetch it from the original source - %% into the project's deps - find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)). - -find_dep_in_dir(Config, _Dep, {false, Dir}) -> - {Config, {missing, Dir}}; -find_dep_in_dir(Config, Dep, {true, Dir}) -> - App = Dep#dep.app, - VsnRegex = Dep#dep.vsn_regex, - IsRaw = Dep#dep.is_raw, - case is_app_available(Config, App, VsnRegex, Dir, IsRaw) of - {Config1, {true, _AppFile}} -> {Config1, {avail, Dir}}; - {Config1, {false, _}} -> {Config1, {missing, Dir}} - end. - -acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) -> - {[Dep#dep { dir = AppDir } | Avail], Missing}; -acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) -> - {Avail, [Dep#dep { dir = AppDir } | Missing]}; -acc_deps(read, _, Dep, AppDir, Acc) -> - [Dep#dep { dir = AppDir } | Acc]. - -delete_dep(D) -> - case filelib:is_dir(D#dep.dir) of - true -> - ?INFO("Deleting dependency: ~s\n", [D#dep.dir]), - rebar_file_utils:rm_rf(D#dep.dir); - false -> - ok - end. - -require_source_engine(Source) -> - true = source_engine_avail(Source), - ok. - -%% IsRaw = false means regular Erlang/OTP dependency -%% -%% IsRaw = true means non-Erlang/OTP dependency, e.g. the one that does not -%% have a proper .app file -is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) -> - ?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]), - case rebar_app_utils:is_app_dir(Path) of - {true, AppFile} -> - case rebar_app_utils:app_name(Config, AppFile) of - {Config1, App} -> - {Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppFile), - ?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n", - [App, VsnRegex, App, Vsn, Path]), - case re:run(Vsn, VsnRegex, [{capture, none}]) of - match -> - {Config2, {true, Path}}; - nomatch -> - ?WARN("~s has version ~p; requested regex was ~s\n", - [AppFile, Vsn, VsnRegex]), - {Config2, - {false, {version_mismatch, - {AppFile, - {expected, VsnRegex}, {has, Vsn}}}}} - end; - {Config1, OtherApp} -> - ?WARN("~s has application id ~p; expected ~p\n", - [AppFile, OtherApp, App]), - {Config1, - {false, {name_mismatch, - {AppFile, {expected, App}, {has, OtherApp}}}}} - end; - false -> - ?WARN("Expected ~s to be an app dir (containing ebin/*.app), " - "but no .app found.\n", [Path]), - {Config, {false, {missing_app_file, Path}}} - end; -is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) -> - ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n", - [App, Path]), - case filelib:is_dir(Path) of - true -> - %% TODO: look for version string in <Path>/VERSION file? Not clear - %% how to detect git/svn/hg/{cmd, ...} settings that can be passed - %% to rebar_utils:vcs_vsn/2 to obtain version dynamically - {Config, {true, Path}}; - false -> - ?WARN("Expected ~s to be a raw dependency directory, " - "but no directory found.\n", [Path]), - {Config, {false, {missing_raw_dependency_directory, Path}}} - end. - -use_source(Config, Dep) -> - use_source(Config, Dep, 3). - -use_source(_Config, Dep, 0) -> - ?ABORT("Failed to acquire source from ~p after 3 tries.\n", - [Dep#dep.source]); -use_source(Config, Dep, Count) -> - case filelib:is_dir(Dep#dep.dir) of - true -> - %% Already downloaded -- verify the versioning matches the regex - case is_app_available(Config, Dep#dep.app, Dep#dep.vsn_regex, - Dep#dep.dir, Dep#dep.is_raw) of - {Config1, {true, _}} -> - Dir = filename:join(Dep#dep.dir, "ebin"), - ok = filelib:ensure_dir(filename:join(Dir, "dummy")), - %% Available version matches up -- we're good to go; - %% add the app dir to our code path - true = code:add_patha(Dir), - {Config1, Dep}; - {_Config1, {false, Reason}} -> - %% The app that was downloaded doesn't match up (or had - %% errors or something). For the time being, abort. - ?ABORT("Dependency dir ~s failed application validation " - "with reason:~n~p.\n", [Dep#dep.dir, Reason]) - end; - false -> - ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), - require_source_engine(Dep#dep.source), - {true, TargetDir} = get_deps_dir(Config, Dep#dep.app), - download_source(TargetDir, Dep#dep.source), - use_source(Config, Dep#dep { dir = TargetDir }, Count-1) - end. - --record(p4_settings, { - client=undefined, - transport="tcp4:perforce:1666", - username, - password - }). -init_p4_settings(Basename) -> - #p4_settings{client = - case inet:gethostname() of - {ok,HostName} -> - HostName ++ "-" - ++ os:getenv("USER") ++ "-" - ++ Basename - ++ "-Rebar-automated-download" - end}. - -download_source(AppDir, {p4, Url}) -> - download_source(AppDir, {p4, Url, "#head"}); -download_source(AppDir, {p4, Url, Rev}) -> - download_source(AppDir, {p4, Url, Rev, init_p4_settings(filename:basename(AppDir))}); -download_source(AppDir, {p4, Url, _Rev, Settings}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh_send("p4 client -i", - ?FMT("Client: ~s~n" - ++"Description: generated by Rebar~n" - ++"Root: ~s~n" - ++"View:~n" - ++" ~s/... //~s/...~n", - [Settings#p4_settings.client, - AppDir, - Url, - Settings#p4_settings.client]), - []), - rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); -download_source(AppDir, {hg, Url, Rev}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]), - rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]); -download_source(AppDir, {git, Url}) -> - download_source(AppDir, {git, Url, {branch, "HEAD"}}); -download_source(AppDir, {git, Url, ""}) -> - download_source(AppDir, {git, Url, {branch, "HEAD"}}); -download_source(AppDir, {git, Url, {branch, Branch}}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]), - rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]); -download_source(AppDir, {git, Url, {tag, Tag}}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]); -download_source(AppDir, {git, Url, Rev}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]), - rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, AppDir}]); -download_source(AppDir, {bzr, Url, Rev}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s", - [Rev, Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]); -download_source(AppDir, {svn, Url, Rev}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s", - [Rev, Url, filename:basename(AppDir)]), - [{cd, filename:dirname(AppDir)}]); -download_source(AppDir, {rsync, Url}) -> - ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []); -download_source(AppDir, {fossil, Url}) -> - download_source(AppDir, {fossil, Url, ""}); -download_source(AppDir, {fossil, Url, Version}) -> - Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"), - ok = filelib:ensure_dir(Repository), - ok = file:set_cwd(AppDir), - rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]), - [{cd, AppDir}]), - rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]), - []). - -update_source(Config, Dep) -> - %% It's possible when updating a source, that a given dep does not have a - %% VCS directory, such as when a source archive is built of a project, with - %% all deps already downloaded/included. So, verify that the necessary VCS - %% directory exists before attempting to do the update. - {true, AppDir} = get_deps_dir(Config, Dep#dep.app), - case has_vcs_dir(element(1, Dep#dep.source), AppDir) of - true -> - ?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), - require_source_engine(Dep#dep.source), - update_source1(AppDir, Dep#dep.source), - Dep; - false -> - ?WARN("Skipping update for ~p: " - "no VCS directory available!\n", [Dep]), - Dep - end. - -update_source1(AppDir, Args) when element(1, Args) =:= p4 -> - download_source(AppDir, Args); -update_source1(AppDir, {git, Url}) -> - update_source1(AppDir, {git, Url, {branch, "HEAD"}}); -update_source1(AppDir, {git, Url, ""}) -> - update_source1(AppDir, {git, Url, {branch, "HEAD"}}); -update_source1(AppDir, {git, _Url, {branch, Branch}}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts), - rebar_utils:sh( - ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts); -update_source1(AppDir, {git, _Url, {tag, Tag}}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts); -update_source1(AppDir, {git, _Url, Refspec}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts); -update_source1(AppDir, {svn, _Url, Rev}) -> - rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {hg, _Url, Rev}) -> - rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {bzr, _Url, Rev}) -> - rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {rsync, Url}) -> - rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]); -update_source1(AppDir, {fossil, Url}) -> - update_source1(AppDir, {fossil, Url, ""}); -update_source1(AppDir, {fossil, _Url, Version}) -> - ok = file:set_cwd(AppDir), - rebar_utils:sh("fossil pull", [{cd, AppDir}]), - rebar_utils:sh(?FMT("fossil update ~s", [Version]), []). - -%% Recursively update deps, this is not done via rebar's usual dep traversal as -%% that is the wrong order (tips are updated before branches). Instead we do a -%% traverse the deps at each level completely before traversing *their* deps. -%% This allows updates to actually propogate down the tree, rather than fail to -%% flow up the tree, which was the previous behaviour. -update_deps_int(Config0, UDD) -> - %% Determine what deps are required - ConfDir = filename:basename(rebar_utils:get_cwd()), - RawDeps = rebar_config:get_local(Config0, deps, []), - {Config1, Deps} = find_deps(Config0, read, RawDeps), - - %% Update each dep - UpdatedDeps = [update_source(Config1, D) - || D <- Deps, D#dep.source =/= undefined, - not lists:member(D, UDD), - not should_skip_update_dep(Config1, D) - ], - - lists:foldl(fun(Dep, {Config, Updated}) -> - {true, AppDir} = get_deps_dir(Config, Dep#dep.app), - Config2 = case has_vcs_dir(element(1, Dep#dep.source), - AppDir) of - false -> - %% If the dep did not exist (maybe it - %% was added), clone it. - %% We'll traverse ITS deps below and - %% clone them if needed. - {C1, _D1} = use_source(Config, Dep), - C1; - true -> - Config - end, - ok = file:set_cwd(AppDir), - Config3 = rebar_config:new(Config2), - %% track where a dep comes from... - DepOwner = dict:append( - Dep, ConfDir, - rebar_config:get_xconf(Config3, depowner, - dict:new())), - Config4 = rebar_config:set_xconf(Config3, depowner, - DepOwner), - - {Config5, Res} = update_deps_int(Config4, Updated), - {Config5, lists:umerge(lists:sort(Res), - lists:sort(Updated))} - end, {Config1, lists:umerge(lists:sort(UpdatedDeps), - lists:sort(UDD))}, UpdatedDeps). - -should_skip_update_dep(Config, Dep) -> - {true, AppDir} = get_deps_dir(Config, Dep#dep.app), - case rebar_app_utils:is_app_dir(AppDir) of - false -> - false; - {true, AppFile} -> - case rebar_app_utils:is_skipped_app(Config, AppFile) of - {_Config, {true, _SkippedApp}} -> - true; - _ -> - false - end - end. - -%% Recursively walk the deps and build a list of them. -collect_deps(Dir, C) -> - case file:set_cwd(Dir) of - ok -> - Config = rebar_config:new(C), - RawDeps = rebar_config:get_local(Config, deps, []), - {Config1, Deps} = find_deps(Config, read, RawDeps), - - lists:flatten(Deps ++ [begin - {true, AppDir} = get_deps_dir( - Config1, Dep#dep.app), - collect_deps(AppDir, C) - end || Dep <- Deps]); - _ -> - [] - end. - - -%% =================================================================== -%% Source helper functions -%% =================================================================== - -source_engine_avail(Source) -> - Name = element(1, Source), - source_engine_avail(Name, Source). - -source_engine_avail(Name, Source) - when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync; - Name == fossil; Name == p4 -> - case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of - true -> - true; - false -> - ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n", - [required_vcs_client_vsn(Name), Name, Source]) - end. - -vcs_client_vsn(false, _VsnArg, _VsnRegex) -> - false; -vcs_client_vsn(Path, VsnArg, VsnRegex) -> - {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]}, - {use_stdout, false}]), - case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of - {match, Match} -> - list_to_tuple([list_to_integer(S) || S <- Match]); - _ -> - false - end. - -required_vcs_client_vsn(p4) -> {2013, 1}; -required_vcs_client_vsn(hg) -> {1, 1}; -required_vcs_client_vsn(git) -> {1, 5}; -required_vcs_client_vsn(bzr) -> {2, 0}; -required_vcs_client_vsn(svn) -> {1, 6}; -required_vcs_client_vsn(rsync) -> {2, 0}; -required_vcs_client_vsn(fossil) -> {1, 0}. - -vcs_client_vsn(p4) -> - vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", - "Rev\\. .*/(\\d+)\\.(\\d)/"); -vcs_client_vsn(hg) -> - vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", - "version (\\d+).(\\d+)"); -vcs_client_vsn(git) -> - vcs_client_vsn(rebar_utils:find_executable("git"), " --version", - "git version (\\d+).(\\d+)"); -vcs_client_vsn(bzr) -> - vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version", - "Bazaar \\(bzr\\) (\\d+).(\\d+)"); -vcs_client_vsn(svn) -> - vcs_client_vsn(rebar_utils:find_executable("svn"), " --version", - "svn, version (\\d+).(\\d+)"); -vcs_client_vsn(rsync) -> - vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version", - "rsync version (\\d+).(\\d+)"); -vcs_client_vsn(fossil) -> - vcs_client_vsn(rebar_utils:find_executable("fossil"), " version", - "version (\\d+).(\\d+)"). - -has_vcs_dir(p4, _) -> - true; -has_vcs_dir(git, Dir) -> - filelib:is_dir(filename:join(Dir, ".git")); -has_vcs_dir(hg, Dir) -> - filelib:is_dir(filename:join(Dir, ".hg")); -has_vcs_dir(bzr, Dir) -> - filelib:is_dir(filename:join(Dir, ".bzr")); -has_vcs_dir(svn, Dir) -> - filelib:is_dir(filename:join(Dir, ".svn")) - orelse filelib:is_dir(filename:join(Dir, "_svn")); -has_vcs_dir(rsync, _) -> - true; -has_vcs_dir(_, _) -> - true. - -print_source(#dep{app=App, source=Source}) -> - ?CONSOLE("~s~n", [format_source(App, Source)]). - -format_source(App, {p4, Url}) -> - format_source(App, {p4, Url, "#head"}); -format_source(App, {git, Url}) -> - ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); -format_source(App, {git, Url, ""}) -> - ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); -format_source(App, {git, Url, {branch, Branch}}) -> - ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]); -format_source(App, {git, Url, {tag, Tag}}) -> - ?FMT("~p TAG ~s ~s", [App, Tag, Url]); -format_source(App, {_, Url, Rev}) -> - ?FMT("~p REV ~s ~s", [App, Rev, Url]); -format_source(App, undefined) -> - ?FMT("~p", [App]). diff --git a/src/rebar_dia_compiler.erl b/src/rebar_dia_compiler.erl deleted file mode 100644 index ba9d159..0000000 --- a/src/rebar_dia_compiler.erl +++ /dev/null @@ -1,106 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2009, 2010 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_dia_compiler). - --export([compile/2, clean/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - --spec compile(rebar_config:config(), file:filename()) -> 'ok'. -compile(Config, _AppFile) -> - rebar_base_compiler:run(Config, filelib:wildcard("dia/*.dia"), - "dia", ".dia", "src", ".erl", - fun compile_dia/3). - --spec clean(rebar_config:config(), file:filename()) -> 'ok'. -clean(_Config, _AppFile) -> - GeneratedFiles = dia_generated_files("dia", "src", "include"), - ok = rebar_file_utils:delete_each(GeneratedFiles), - ok. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - info_help("Build Diameter (*.dia) sources"); -info(help, clean) -> - info_help("Delete generated Diameter files"). - -info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " {dia_opts, []} (see diameter_codegen:from_dict/4 documentation)~n", - [Description]). - --spec compile_dia(file:filename(), file:filename(), - rebar_config:config()) -> ok. -compile_dia(Source, Target, Config) -> - ok = filelib:ensure_dir(Target), - ok = filelib:ensure_dir(filename:join("include", "dummy.hrl")), - Opts = [{outdir, "src"}] ++ rebar_config:get(Config, dia_opts, []), - case diameter_dict_util:parse({path, Source}, []) of - {ok, Spec} -> - FileName = dia_filename(Source, Spec), - _ = diameter_codegen:from_dict(FileName, Spec, Opts, erl), - _ = diameter_codegen:from_dict(FileName, Spec, Opts, hrl), - HrlFile = filename:join("src", FileName ++ ".hrl"), - case filelib:is_regular(HrlFile) of - true -> - ok = rebar_file_utils:mv(HrlFile, "include"); - false -> - ok - end; - {error, Reason} -> - ?ERROR("~s~n", [diameter_dict_util:format_error(Reason)]) - end. - -dia_generated_files(DiaDir, SrcDir, IncDir) -> - F = fun(File, Acc) -> - {ok, Spec} = diameter_dict_util:parse({path, File}, []), - FileName = dia_filename(File, Spec), - [filename:join([IncDir, FileName ++ ".hrl"]) | - filelib:wildcard(filename:join([SrcDir, FileName ++ ".*"]))] ++ Acc - end, - lists:foldl(F, [], filelib:wildcard(filename:join([DiaDir, "*.dia"]))). - -dia_filename(File, Spec) -> - case proplists:get_value(name, Spec) of - undefined -> - filename:rootname(filename:basename(File)); - Name -> - Name - end. diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 6ae927d..ca670ac 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -91,8 +91,8 @@ %% 'old_inets'}]}. %% --spec compile(rebar_config:config(), file:filename()) -> 'ok'. -compile(Config, _AppFile) -> +-spec compile(rebar_config:config(), file:name()) -> 'ok'. +compile(Config, Dir) -> rebar_base_compiler:run(Config, check_files(rebar_config:get_local( Config, xrl_first_files, [])), @@ -108,7 +108,7 @@ compile(Config, _AppFile) -> Config, mib_first_files, [])), "mibs", ".mib", "priv/mibs", ".bin", fun compile_mib/3), - doterl_compile(Config, "ebin"). + doterl_compile(Config, Dir). -spec clean(rebar_config:config(), file:filename()) -> 'ok'. clean(Config, _AppFile) -> @@ -159,10 +159,10 @@ test_compile(Config, Cmd, OutDir) -> end, [], SrcDirs), %% If it is not the first time rebar eunit or rebar qc is executed, - %% there will be source files already present in OutDir. Since some + %% there will be source files already present in Dir. Since some %% SCMs (like Perforce) set the source files as being read only (unless %% they are checked out), we need to be sure that the files already - %% present in OutDir are writable before doing the copy. This is done + %% present in Dir are writable before doing the copy. This is done %% here by removing any file that was already present before calling %% rebar_file_utils:cp_r. @@ -290,17 +290,20 @@ is_lib_avail(Config, DictKey, Mod, Hrl, Name) -> end. -spec doterl_compile(rebar_config:config(), file:filename()) -> 'ok'. -doterl_compile(Config, OutDir) -> +doterl_compile(Config, Dir) -> ErlOpts = rebar_utils:erl_opts(Config), - doterl_compile(Config, OutDir, [], ErlOpts). + doterl_compile(Config, Dir, [], ErlOpts). -doterl_compile(Config, OutDir, MoreSources, ErlOpts) -> - ErlFirstFilesConf = rebar_config:get_list(Config, erl_first_files, []), +doterl_compile(Config, Dir, MoreSources, ErlOpts) -> + OutDir = filename:join(Dir, "ebin"), + ErlFirstFilesConf = rebar_config:get_list(Config, erl_first_modules, []), ?DEBUG("erl_opts ~p~n", [ErlOpts]), %% Support the src_dirs option allowing multiple directories to %% contain erlang source. This might be used, for example, should %% eunit tests be separated from the core application source. - SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)), + SrcDirs = lists:map(fun(X) -> + filename:join(Dir, X) + end, rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts))), AllErlFiles = gather_src(SrcDirs, []) ++ MoreSources, %% NOTE: If and when erl_first_files is not inherited anymore %% (rebar_config:get_local instead of rebar_config:get_list), consider @@ -309,12 +312,12 @@ doterl_compile(Config, OutDir, MoreSources, ErlOpts) -> {ErlFirstFiles, RestErls} = lists:partition( fun(Source) -> - lists:member(Source, ErlFirstFilesConf) + lists:member(list_to_atom(filename:basename(Source, ".erl")), ErlFirstFilesConf) end, AllErlFiles), %% Make sure that ebin/ exists and is on the path - ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")), + ok = filelib:ensure_dir(filename:join([Dir, "ebin", "dummy.beam"])), CurrPath = code:get_path(), - true = code:add_path(filename:absname("ebin")), + true = code:add_path(filename:absname(filename:join(Dir, "ebin"))), OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir), G = init_erlcinfo(Config, AllErlFiles), %% Split RestErls so that files which are depended on are treated @@ -353,7 +356,7 @@ doterl_compile(Config, OutDir, MoreSources, ErlOpts) -> rebar_base_compiler:run( Config, FirstErls, OtherErls, fun(S, C) -> - internal_erl_compile(C, S, OutDir1, ErlOpts, G) + internal_erl_compile(C, Dir, S, OutDir1, ErlOpts, G) end), true = code:set_path(CurrPath), ok. @@ -394,7 +397,8 @@ u_add_element(Elem, []) -> [Elem]. rebar_config:config()) -> [file:filename(), ...]. include_path(Source, Config) -> ErlOpts = rebar_config:get(Config, erl_opts, []), - lists:usort(["include", filename:dirname(Source)] + Dir = filename:join(lists:droplast(filename:split(filename:dirname(Source)))), + lists:usort([filename:join(Dir, "include"), filename:dirname(Source)] ++ proplists:get_all_values(i, ErlOpts)). -spec needs_compile(file:filename(), file:filename(), @@ -558,10 +562,10 @@ get_children(G, Source) -> %% Return all files dependent on the Source. digraph_utils:reaching_neighbours([Source], G). --spec internal_erl_compile(rebar_config:config(), file:filename(), +-spec internal_erl_compile(rebar_config:config(), file:filename(), file:filename(), file:filename(), list(), rebar_digraph()) -> 'ok' | 'skipped'. -internal_erl_compile(Config, Source, OutDir, ErlOpts, G) -> +internal_erl_compile(Config, Dir, Source, OutDir, ErlOpts, G) -> %% Determine the target name and includes list by inspecting the source file Module = filename:basename(Source, ".erl"), Parents = get_parents(G, Source), @@ -576,7 +580,7 @@ internal_erl_compile(Config, Source, OutDir, ErlOpts, G) -> case needs_compile(Source, Target, Parents) of true -> Opts = [{outdir, filename:dirname(Target)}] ++ - ErlOpts ++ [{i, "include"}, return], + ErlOpts ++ [{i, filename:join(Dir, "include")}, return], case compile:file(Source, Opts) of {ok, _Mod} -> ok; diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl index 10387f5..fac8493 100644 --- a/src/rebar_erlydtl_compiler.erl +++ b/src/rebar_erlydtl_compiler.erl @@ -94,37 +94,56 @@ %% ]}. -module(rebar_erlydtl_compiler). --export([compile/2]). +-behaviour(rebar_provider). + +-export([init/1, + do/1]). %% for internal use only -export([info/2]). -include("rebar.hrl"). +-define(PROVIDER, erlydtl). +-define(DEPS, [app_builder]). + %% =================================================================== %% Public API %% =================================================================== -compile(Config, _AppFile) -> +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = compile, + bare = false, + deps = ?DEPS, + example = "compile", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +do(Config) -> MultiDtlOpts = erlydtl_opts(Config), OrigPath = code:get_path(), true = code:add_path(rebar_utils:ebin_dir()), Result = lists:foldl(fun(DtlOpts, _) -> - rebar_base_compiler:run(Config, [], - option(doc_root, DtlOpts), - option(source_ext, DtlOpts), - option(out_dir, DtlOpts), - option(module_ext, DtlOpts) ++ ".beam", - fun(S, T, C) -> - compile_dtl(C, S, T, DtlOpts) - end, - [{check_last_mod, false}, - {recursive, option(recursive, DtlOpts)}]) - end, ok, MultiDtlOpts), + rebar_base_compiler:run(Config, [], + option(doc_root, DtlOpts), + option(source_ext, DtlOpts), + option(out_dir, DtlOpts), + option(module_ext, DtlOpts) ++ ".beam", + fun(S, T, C) -> + compile_dtl(C, S, T, DtlOpts) + end, + [{check_last_mod, false}, + {recursive, option(recursive, DtlOpts)}]) + end, ok, MultiDtlOpts), true = code:set_path(OrigPath), - Result. + {Result, Config}. %% =================================================================== %% Internal functions diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl index 0cc43ef..395475f 100644 --- a/src/rebar_escripter.erl +++ b/src/rebar_escripter.erl @@ -26,6 +26,11 @@ %% ------------------------------------------------------------------- -module(rebar_escripter). +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + -export([escriptize/2, clean/2]). @@ -35,10 +40,33 @@ -include("rebar.hrl"). -include_lib("kernel/include/file.hrl"). +-define(PROVIDER, escriptize). +-define(DEPS, [app_builder]). + %% =================================================================== %% Public API %% =================================================================== +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = escriptize, + bare = false, + deps = ?DEPS, + example = "escriptize", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + AppName = rebar_config:get_local(Config, escript_top_level_app, undefined), + App = rebar_config: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 diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl new file mode 100644 index 0000000..6ed0f3f --- /dev/null +++ b/src/rebar_fetch.erl @@ -0,0 +1,247 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% ------------------------------------------------------------------- +-module(rebar_fetch). + +-export([new/4, + current_ref/2, + download_source/2, + update_source1/2, + source_engine_avail/1, + source_engine_avail/2, + has_vcs_dir/2, + print_source/1, + format_source/2]). + +-include("rebar.hrl"). + +-type dep_name() :: atom(). +-type dep_vsn() :: ec_semver:any_version(). +-type dep_source() :: {atom(), string(), ref()}. +-type ref() :: string() | {atom(), string()}. + +-type dep() :: { dep_name(), dep_vsn(), dep_source() }. + +-record(p4_settings, { + client=undefined, + transport="tcp4:perforce:1666", + username, + password + }). + +new(Dir, App, Vsn, Source) -> + Ref = current_ref(Dir, Source), + {App, Vsn, setelement(3, Source, Ref)}. + +init_p4_settings(Basename) -> + #p4_settings{client = + case inet:gethostname() of + {ok,HostName} -> + HostName ++ "-" + ++ os:getenv("USER") ++ "-" + ++ Basename + ++ "-Rebar-automated-download" + end}. + +current_ref(AppDir, {git, _, _}) -> + string:strip(os:cmd("git --git-dir='" ++ AppDir ++ "/.git' rev-parse --verify HEAD"), both, $\n). + +download_source(AppDir, {p4, Url}) -> + download_source(AppDir, {p4, Url, "#head"}); +download_source(AppDir, {p4, Url, Rev}) -> + download_source(AppDir, {p4, Url, Rev, init_p4_settings(filename:basename(AppDir))}); +download_source(AppDir, {p4, Url, _Rev, Settings}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh_send("p4 client -i", + ?FMT("Client: ~s~n" + ++"Description: generated by Rebar~n" + ++"Root: ~s~n" + ++"View:~n" + ++" ~s/... //~s/...~n", + [Settings#p4_settings.client, + AppDir, + Url, + Settings#p4_settings.client]), + []), + rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); +download_source(AppDir, {hg, Url, Rev}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]); +download_source(AppDir, {git, Url}) -> + download_source(AppDir, {git, Url, {branch, "HEAD"}}); +download_source(AppDir, {git, Url, ""}) -> + download_source(AppDir, {git, Url, {branch, "HEAD"}}); +download_source(AppDir, {git, Url, {branch, Branch}}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]); +download_source(AppDir, {git, Url, {tag, Tag}}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]); +download_source(AppDir, {git, Url, Rev}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, AppDir}]); +download_source(AppDir, {bzr, Url, Rev}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s", + [Rev, Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]); +download_source(AppDir, {svn, Url, Rev}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s", + [Rev, Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]); +download_source(AppDir, {rsync, Url}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []); +download_source(AppDir, {fossil, Url}) -> + download_source(AppDir, {fossil, Url, ""}); +download_source(AppDir, {fossil, Url, Version}) -> + Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"), + ok = filelib:ensure_dir(Repository), + ok = file:set_cwd(AppDir), + rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]), + [{cd, AppDir}]), + rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]), + []). + +update_source1(AppDir, Args) when element(1, Args) =:= p4 -> + download_source(AppDir, Args); +update_source1(AppDir, {git, Url}) -> + update_source1(AppDir, {git, Url, {branch, "HEAD"}}); +update_source1(AppDir, {git, Url, ""}) -> + update_source1(AppDir, {git, Url, {branch, "HEAD"}}); +update_source1(AppDir, {git, _Url, {branch, Branch}}) -> + ShOpts = [{cd, AppDir}], + rebar_utils:sh("git fetch origin", ShOpts), + rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts), + rebar_utils:sh( + ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts); +update_source1(AppDir, {git, _Url, {tag, Tag}}) -> + ShOpts = [{cd, AppDir}], + rebar_utils:sh("git fetch origin", ShOpts), + rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts); +update_source1(AppDir, {git, _Url, Refspec}) -> + ShOpts = [{cd, AppDir}], + rebar_utils:sh("git fetch origin", ShOpts), + rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts); +update_source1(AppDir, {svn, _Url, Rev}) -> + rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]); +update_source1(AppDir, {hg, _Url, Rev}) -> + rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]); +update_source1(AppDir, {bzr, _Url, Rev}) -> + rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]); +update_source1(AppDir, {rsync, Url}) -> + rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]); +update_source1(AppDir, {fossil, Url}) -> + update_source1(AppDir, {fossil, Url, ""}); +update_source1(AppDir, {fossil, _Url, Version}) -> + ok = file:set_cwd(AppDir), + rebar_utils:sh("fossil pull", [{cd, AppDir}]), + rebar_utils:sh(?FMT("fossil update ~s", [Version]), []). + +%% =================================================================== +%% Source helper functions +%% =================================================================== + +source_engine_avail(Source) -> + Name = element(1, Source), + source_engine_avail(Name, Source). + +source_engine_avail(Name, Source) + when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync; + Name == fossil; Name == p4 -> + case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of + true -> + true; + false -> + ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n", + [required_vcs_client_vsn(Name), Name, Source]) + end. + +vcs_client_vsn(false, _VsnArg, _VsnRegex) -> + false; +vcs_client_vsn(Path, VsnArg, VsnRegex) -> + {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]}, + {use_stdout, false}]), + case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of + {match, Match} -> + list_to_tuple([list_to_integer(S) || S <- Match]); + _ -> + false + end. + +required_vcs_client_vsn(p4) -> {2013, 1}; +required_vcs_client_vsn(hg) -> {1, 1}; +required_vcs_client_vsn(git) -> {1, 5}; +required_vcs_client_vsn(bzr) -> {2, 0}; +required_vcs_client_vsn(svn) -> {1, 6}; +required_vcs_client_vsn(rsync) -> {2, 0}; +required_vcs_client_vsn(fossil) -> {1, 0}. + +vcs_client_vsn(p4) -> + vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", + "Rev\\. .*/(\\d+)\\.(\\d)/"); +vcs_client_vsn(hg) -> + vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", + "version (\\d+).(\\d+)"); +vcs_client_vsn(git) -> + vcs_client_vsn(rebar_utils:find_executable("git"), " --version", + "git version (\\d+).(\\d+)"); +vcs_client_vsn(bzr) -> + vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version", + "Bazaar \\(bzr\\) (\\d+).(\\d+)"); +vcs_client_vsn(svn) -> + vcs_client_vsn(rebar_utils:find_executable("svn"), " --version", + "svn, version (\\d+).(\\d+)"); +vcs_client_vsn(rsync) -> + vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version", + "rsync version (\\d+).(\\d+)"); +vcs_client_vsn(fossil) -> + vcs_client_vsn(rebar_utils:find_executable("fossil"), " version", + "version (\\d+).(\\d+)"). + +has_vcs_dir(p4, _) -> + true; +has_vcs_dir(git, Dir) -> + filelib:is_dir(filename:join(Dir, ".git")); +has_vcs_dir(hg, Dir) -> + filelib:is_dir(filename:join(Dir, ".hg")); +has_vcs_dir(bzr, Dir) -> + filelib:is_dir(filename:join(Dir, ".bzr")); +has_vcs_dir(svn, Dir) -> + filelib:is_dir(filename:join(Dir, ".svn")) + orelse filelib:is_dir(filename:join(Dir, "_svn")); +has_vcs_dir(rsync, _) -> + true; +has_vcs_dir(_, _) -> + true. + +print_source({App, _, Source}) -> + ?CONSOLE("~s~n", [format_source(App, Source)]). + +format_source(App, {p4, Url}) -> + format_source(App, {p4, Url, "#head"}); +format_source(App, {git, Url}) -> + ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); +format_source(App, {git, Url, ""}) -> + ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); +format_source(App, {git, Url, {branch, Branch}}) -> + ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]); +format_source(App, {git, Url, {tag, Tag}}) -> + ?FMT("~p TAG ~s ~s", [App, Tag, Url]); +format_source(App, {_, Url, Rev}) -> + ?FMT("~p REV ~s ~s", [App, Rev, Url]); +format_source(App, undefined) -> + ?FMT("~p", [App]). diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl deleted file mode 100644 index 8488b0f..0000000 --- a/src/rebar_lfe_compiler.erl +++ /dev/null @@ -1,84 +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), -%% Tim Dysinger (tim@dysinger.net) -%% -%% 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_lfe_compiler). - --export([compile/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - -compile(Config, _AppFile) -> - FirstFiles = rebar_config:get_list(Config, lfe_first_files, []), - rebar_base_compiler:run(Config, FirstFiles, "src", ".lfe", "ebin", ".beam", - fun compile_lfe/3). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - ?CONSOLE( - "Build Lisp Flavoured Erlang (*.lfe) sources.~n" - "~n" - "Valid rebar.config options:~n" - " erl_opts is reused.'~n", - []). - -compile_lfe(Source, _Target, Config) -> - case code:which(lfe_comp) of - non_existing -> - ?ERROR("~n" - "*** MISSING LFE COMPILER ***~n" - " You must do one of the following:~n" - " a) Install LFE globally in your erl libs~n" - " b) Add LFE as a dep for your project, eg:~n" - " {lfe, \"0.6.1\",~n" - " {git, \"git://github.com/rvirding/lfe\",~n" - " {tag, \"v0.6.1\"}}}~n" - "~n", []), - ?FAIL; - _ -> - ErlOpts = rebar_utils:erl_opts(Config), - Opts = [{i, "include"}, {outdir, "ebin"}, return] ++ ErlOpts, - case lfe_comp:file(Source, Opts) of - {ok, _Mod, Ws} -> - rebar_base_compiler:ok_tuple(Config, Source, Ws); - {error, Es, Ws} -> - rebar_base_compiler:error_tuple(Config, Source, - Es, Ws, Opts); - _ -> - ?FAIL - end - end. diff --git a/src/rebar_metacmds.erl b/src/rebar_metacmds.erl deleted file mode 100644 index 6e223bd..0000000 --- a/src/rebar_metacmds.erl +++ /dev/null @@ -1,56 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2013-2014 Tuncer Ayaz -%% -%% 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_metacmds). - --export(['prepare-deps'/2, - 'refresh-deps'/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== -'prepare-deps'(Config, _AppFile) -> - rebar:run(enable_recursion(Config), ["get-deps", "compile"]). - -'refresh-deps'(Config, _AppFile) -> - rebar:run(enable_recursion(Config), ["update-deps", "compile"]). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, 'prepare-deps') -> - ?CONSOLE("Meta command to run 'rebar -r get-deps compile'.~n", []); -info(help, 'refresh-deps') -> - ?CONSOLE("Meta command to run 'rebar -r update-deps compile'.~n", []). - -enable_recursion(Config) -> - rebar_config:set_xconf(Config, recursive, true). diff --git a/src/rebar_mustache.erl b/src/rebar_mustache.erl deleted file mode 100644 index 9016c0f..0000000 --- a/src/rebar_mustache.erl +++ /dev/null @@ -1,230 +0,0 @@ -%% The MIT License -%% -%% Copyright (c) 2009 Tom Preston-Werner <tom@mojombo.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. - -%% See the README at http://github.com/mojombo/mustache.erl for additional -%% documentation and usage examples. - --module(rebar_mustache). %% v0.1.0 --author("Tom Preston-Werner"). --export([compile/1, compile/2, render/1, render/2, render/3, get/2, get/3, escape/1, start/1]). - --record(mstate, {mod = undefined, - section_re = undefined, - tag_re = undefined}). - --define(MUSTACHE_STR, "rebar_mustache"). - -compile(Body) when is_list(Body) -> - State = #mstate{}, - CompiledTemplate = pre_compile(Body, State), - % io:format("~p~n~n", [CompiledTemplate]), - % io:format(CompiledTemplate ++ "~n", []), - {ok, Tokens, _} = erl_scan:string(CompiledTemplate), - {ok, [Form]} = erl_parse:parse_exprs(Tokens), - Bindings = erl_eval:new_bindings(), - {value, Fun, _} = erl_eval:expr(Form, Bindings), - Fun; -compile(Mod) -> - TemplatePath = template_path(Mod), - compile(Mod, TemplatePath). - -compile(Mod, File) -> - code:purge(Mod), - {module, _} = code:load_file(Mod), - {ok, TemplateBin} = file:read_file(File), - Template = re:replace(TemplateBin, "\"", "\\\\\"", [global, {return,list}]), - State = #mstate{mod = Mod}, - CompiledTemplate = pre_compile(Template, State), - % io:format("~p~n~n", [CompiledTemplate]), - % io:format(CompiledTemplate ++ "~n", []), - {ok, Tokens, _} = erl_scan:string(CompiledTemplate), - {ok, [Form]} = erl_parse:parse_exprs(Tokens), - Bindings = erl_eval:new_bindings(), - {value, Fun, _} = erl_eval:expr(Form, Bindings), - Fun. - -render(Mod) -> - TemplatePath = template_path(Mod), - render(Mod, TemplatePath). - -render(Body, Ctx) when is_list(Body) -> - TFun = compile(Body), - render(undefined, TFun, Ctx); -render(Mod, File) when is_list(File) -> - render(Mod, File, dict:new()); -render(Mod, CompiledTemplate) -> - render(Mod, CompiledTemplate, dict:new()). - -render(Mod, File, Ctx) when is_list(File) -> - CompiledTemplate = compile(Mod, File), - render(Mod, CompiledTemplate, Ctx); -render(Mod, CompiledTemplate, Ctx) -> - Ctx2 = dict:store('__mod__', Mod, Ctx), - lists:flatten(CompiledTemplate(Ctx2)). - -pre_compile(T, State) -> - SectionRE = "\{\{\#([^\}]*)}}\s*(.+?){{\/\\1\}\}\s*", - {ok, CompiledSectionRE} = re:compile(SectionRE, [dotall]), - TagRE = "\{\{(#|=|!|<|>|\{)?(.+?)\\1?\}\}+", - {ok, CompiledTagRE} = re:compile(TagRE, [dotall]), - State2 = State#mstate{section_re = CompiledSectionRE, tag_re = CompiledTagRE}, - "fun(Ctx) -> " ++ - "CFun = fun(A, B) -> A end, " ++ - compiler(T, State2) ++ " end.". - -compiler(T, State) -> - Res = re:run(T, State#mstate.section_re), - case Res of - {match, [{M0, M1}, {N0, N1}, {C0, C1}]} -> - Front = string:substr(T, 1, M0), - Back = string:substr(T, M0 + M1 + 1), - Name = string:substr(T, N0 + 1, N1), - Content = string:substr(T, C0 + 1, C1), - "[" ++ compile_tags(Front, State) ++ - " | [" ++ compile_section(Name, Content, State) ++ - " | [" ++ compiler(Back, State) ++ "]]]"; - nomatch -> - compile_tags(T, State) - end. - -compile_section(Name, Content, State) -> - Mod = State#mstate.mod, - Result = compiler(Content, State), - "fun() -> " ++ - "case " ++ ?MUSTACHE_STR ++ ":get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++ - "\"true\" -> " ++ - Result ++ "; " ++ - "\"false\" -> " ++ - "[]; " ++ - "List when is_list(List) -> " ++ - "[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++ - "Else -> " ++ - "throw({template, io_lib:format(\"Bad context for ~p: ~p\", [" ++ Name ++ ", Else])}) " ++ - "end " ++ - "end()". - -compile_tags(T, State) -> - Res = re:run(T, State#mstate.tag_re), - case Res of - {match, [{M0, M1}, K, {C0, C1}]} -> - Front = string:substr(T, 1, M0), - Back = string:substr(T, M0 + M1 + 1), - Content = string:substr(T, C0 + 1, C1), - Kind = tag_kind(T, K), - Result = compile_tag(Kind, Content, State), - "[\"" ++ Front ++ - "\" | [" ++ Result ++ - " | " ++ compile_tags(Back, State) ++ "]]"; - nomatch -> - "[\"" ++ T ++ "\"]" - end. - -tag_kind(_T, {-1, 0}) -> - none; -tag_kind(T, {K0, K1}) -> - string:substr(T, K0 + 1, K1). - -compile_tag(none, Content, State) -> - Mod = State#mstate.mod, - ?MUSTACHE_STR ++ ":escape(" ++ ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))"; -compile_tag("{", Content, State) -> - Mod = State#mstate.mod, - ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")"; -compile_tag("!", _Content, _State) -> - "[]". - -template_dir(Mod) -> - DefaultDirPath = filename:dirname(code:which(Mod)), - case application:get_env(mustache, templates_dir) of - {ok, DirPath} when is_list(DirPath) -> - case filelib:ensure_dir(DirPath) of - ok -> DirPath; - _ -> DefaultDirPath - end; - _ -> - DefaultDirPath - end. -template_path(Mod) -> - DirPath = template_dir(Mod), - Basename = atom_to_list(Mod), - filename:join(DirPath, Basename ++ ".mustache"). - -get(Key, Ctx) when is_list(Key) -> - {ok, Mod} = dict:find('__mod__', Ctx), - get(list_to_atom(Key), Ctx, Mod); -get(Key, Ctx) -> - {ok, Mod} = dict:find('__mod__', Ctx), - get(Key, Ctx, Mod). - -get(Key, Ctx, Mod) when is_list(Key) -> - get(list_to_atom(Key), Ctx, Mod); -get(Key, Ctx, Mod) -> - case dict:find(Key, Ctx) of - {ok, Val} -> - % io:format("From Ctx {~p, ~p}~n", [Key, Val]), - to_s(Val); - error -> - case erlang:function_exported(Mod, Key, 1) of - true -> - Val = to_s(Mod:Key(Ctx)), - % io:format("From Mod/1 {~p, ~p}~n", [Key, Val]), - Val; - false -> - case erlang:function_exported(Mod, Key, 0) of - true -> - Val = to_s(Mod:Key()), - % io:format("From Mod/0 {~p, ~p}~n", [Key, Val]), - Val; - false -> - [] - end - end - end. - -to_s(Val) when is_integer(Val) -> - integer_to_list(Val); -to_s(Val) when is_float(Val) -> - io_lib:format("~.2f", [Val]); -to_s(Val) when is_atom(Val) -> - atom_to_list(Val); -to_s(Val) -> - Val. - -escape(HTML) -> - escape(HTML, []). - -escape([], Acc) -> - lists:reverse(Acc); -escape(["<" | Rest], Acc) -> - escape(Rest, lists:reverse("<", Acc)); -escape([">" | Rest], Acc) -> - escape(Rest, lists:reverse(">", Acc)); -escape(["&" | Rest], Acc) -> - escape(Rest, lists:reverse("&", Acc)); -escape([X | Rest], Acc) -> - escape(Rest, [X | Acc]). - -%%--------------------------------------------------------------------------- - -start([T]) -> - Out = render(list_to_atom(T)), - io:format(Out ++ "~n", []). diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl deleted file mode 100644 index 5549dc4..0000000 --- a/src/rebar_neotoma_compiler.erl +++ /dev/null @@ -1,163 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2010 Cliff Moon (cliff@moonpolysoft.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. -%% ------------------------------------------------------------------- - -%% The rebar_neotoma module is a plugin for rebar that compiles -%% neotoma peg files. By default, it compiles all src/*.peg to src/*.erl -%% -%% Configuration options should be placed in rebar.config under -%% neotoma_opts. Available options include: -%% -%% doc_root: where to find the peg files to compile. -%% "src" by default -%% out_dir: where to put the generated erl files. -%% "src" by defualt -%% module_ext: characters to append to the module's name. -%% "" by default -%% source_ext: extension of peg source files --module(rebar_neotoma_compiler). - --export([compile/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% ============================================================================ -%% Public API -%% ============================================================================ - -compile(Config, _AppFile) -> - NeoOpts = neotoma_opts(Config), - rebar_base_compiler:run(Config, [], - option(doc_root, NeoOpts), ".peg", - option(out_dir, NeoOpts), - option(module_ext, NeoOpts) ++ ".erl", - fun compile_neo/3, [{check_last_mod, true}]). - -%% ============================================================================ -%% Internal functions -%% ============================================================================ - -info(help, compile) -> - ?CONSOLE( - "Build Neotoma (*.peg) sources.~n" - "~n" - "Valid rebar.config options:~n" - " ~p~n", - [ - {neotoma_opts, [{doc_root, "src"}, - {out_dir, "src"}, - {source_ext, ".peg"}, - {module_ext, ""}]} - ]). - -neotoma_opts(Config) -> - rebar_config:get(Config, neotoma_opts, []). - -option(Opt, Options) -> - proplists:get_value(Opt, Options, default(Opt)). - -default(doc_root) -> "src"; -default(out_dir) -> "src"; -default(module_ext) -> ""; -default(source_ext) -> ".peg". - -compile_neo(Source, Target, Config) -> - case code:which(neotoma) of - non_existing -> - ?ERROR("~n===============================================~n" - " You need to install neotoma to compile PEG grammars~n" - " Download the latest tarball release from github~n" - " https://github.com/seancribbs/neotoma~n" - " and install it into your erlang library dir~n" - "===============================================~n~n", []), - ?FAIL; - _ -> - case needs_compile(Source, Target, Config) of - true -> - do_compile(Source, Target, Config); - false -> - skipped - end - end. - -do_compile(Source, _Target, Config) -> - %% TODO: Check last mod on target and referenced DTLs here.. - NeoOpts = neotoma_opts(Config), - %% ensure that doc_root and out_dir are defined, - %% using defaults if necessary - Opts = [{output, option(out_dir, NeoOpts)}, - {module, list_to_atom(filename:basename(Source, ".peg") - ++ option(module_ext, NeoOpts))}], - case neotoma:file(Source, Opts ++ NeoOpts) of - ok -> - ok; - Reason -> - ?ERROR("Compiling peg ~s failed:~n ~p~n", - [Source, Reason]), - ?FAIL - end. - -needs_compile(Source, Target, Config) -> - LM = filelib:last_modified(Target), - LM < filelib:last_modified(Source) orelse - lists:any(fun(D) -> LM < filelib:last_modified(D) end, - referenced_pegs(Source, Config)). - -referenced_pegs(Source, Config) -> - Set = referenced_pegs1([Source], Config, - sets:add_element(Source, sets:new())), - sets:to_list(sets:del_element(Source, Set)). - -referenced_pegs1(Step, Config, Seen) -> - NeoOpts = neotoma_opts(Config), - ExtMatch = re:replace(option(source_ext, NeoOpts), "\.", "\\\\\\\\.", - [{return, list}]), - - ShOpts = [{use_stdout, false}, return_on_error], - AllRefs = - lists:append( - [begin - Cmd = lists:flatten(["grep -o [^\\\"]*", - ExtMatch, " ", F]), - case rebar_utils:sh(Cmd, ShOpts) of - {ok, Res} -> - string:tokens(Res, "\n"); - {error, _} -> - "" - end - end || F <- Step]), - DocRoot = option(doc_root, NeoOpts), - WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ], - Existing = [F || F <- WithPaths, filelib:is_regular(F)], - New = sets:subtract(sets:from_list(Existing), Seen), - case sets:size(New) of - 0 -> Seen; - _ -> referenced_pegs1(sets:to_list(New), Config, - sets:union(New, Seen)) - end. diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl index b3566c8..e390f3b 100644 --- a/src/rebar_otp_app.erl +++ b/src/rebar_otp_app.erl @@ -38,31 +38,33 @@ %% Public API %% =================================================================== -compile(Config, File) -> +compile(Config, App) -> %% If we get an .app.src file, it needs to be pre-processed and %% written out as a ebin/*.app file. That resulting file will then %% be validated as usual. - {Config1, AppFile} = case rebar_app_utils:is_app_src(File) of - true -> - preprocess(Config, File); - false -> - {Config, File} - end, + Dir = rebar_app_info:dir(App), + {Config2, App1} = case rebar_app_info:app_file_src(App) of + undefined -> + {Config, App}; + AppFileSrc -> + {Config1, File} = preprocess(Config, Dir, AppFileSrc), + {Config1, rebar_app_info:app_file(App, File)} + end, %% Load the app file and validate it. - case rebar_app_utils:load_app_file(Config1, AppFile) of - {ok, Config2, AppName, AppData} -> + AppFile = rebar_app_info:app_file(App1), + case rebar_app_utils:load_app_file(Config2, AppFile) of + {ok, Config3, AppName, AppData} -> validate_name(AppName, AppFile), - %% In general, the list of modules is an important thing to validate %% for compliance with OTP guidelines and upgrade procedures. %% However, some people prefer not to validate this list. - case rebar_config:get_local(Config1, validate_app_modules, true) of + case rebar_config:get_local(Config3, validate_app_modules, true) of true -> Modules = proplists:get_value(modules, AppData), - {validate_modules(AppName, Modules), Config2}; + {validate_modules(Dir, AppName, Modules), App1}; false -> - {ok, Config2} + {ok, App1} end; {error, Reason} -> ?ABORT("Failed to load app file ~s: ~p\n", [AppFile, Reason]) @@ -105,13 +107,13 @@ info_help(Description) -> {validate_app_modules, true} ]). -preprocess(Config, AppSrcFile) -> +preprocess(Config, Dir, AppSrcFile) -> case rebar_app_utils:load_app_file(Config, AppSrcFile) of {ok, Config1, AppName, AppData} -> %% Look for a configuration file with vars we want to %% substitute. Note that we include the list of modules available in %% ebin/ and update the app data accordingly. - AppVars = load_app_vars(Config1) ++ [{modules, ebin_modules()}], + AppVars = load_app_vars(Config1) ++ [{modules, ebin_modules(Dir)}], A1 = apply_app_vars(AppVars, AppData), @@ -171,15 +173,15 @@ validate_name(AppName, File) -> ?FAIL end. -validate_modules(AppName, undefined) -> +validate_modules(_Dir, AppName, undefined) -> ?ERROR("Missing modules declaration in ~p.app~n", [AppName]), ?FAIL; -validate_modules(AppName, Mods) -> +validate_modules(Dir, AppName, Mods) -> %% Construct two sets -- one for the actual .beam files in ebin/ %% and one for the modules %% listed in the .app file - EbinSet = ordsets:from_list(ebin_modules()), + EbinSet = ordsets:from_list(ebin_modules(Dir)), ModSet = ordsets:from_list(Mods), %% Identify .beam files listed in the .app, but not present in ebin/ @@ -206,9 +208,9 @@ validate_modules(AppName, Mods) -> ?FAIL end. -ebin_modules() -> - lists:sort([rebar_utils:beam_to_mod("ebin", N) || - N <- rebar_utils:beams("ebin")]). +ebin_modules(Dir) -> + lists:sort([rebar_utils:beam_to_mod(N) || + N <- rebar_utils:beams(filename:join(Dir, "ebin"))]). ensure_registered(AppData) -> case lists:keyfind(registered, 1, AppData) of diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl deleted file mode 100644 index 35adc3c..0000000 --- a/src/rebar_port_compiler.erl +++ /dev/null @@ -1,617 +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_port_compiler). - --export([compile/2, - clean/2]). - -%% for internal use only --export([setup_env/1, - info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - -%% Supported configuration variables: -%% -%% * port_specs - Erlang list of tuples of the forms -%% {ArchRegex, TargetFile, Sources, Options} -%% {ArchRegex, TargetFile, Sources} -%% {TargetFile, Sources} -%% -%% * port_env - Erlang list of key/value pairs which will control -%% the environment when running the compiler and linker. -%% -%% By default, the following variables are defined: -%% CC - C compiler -%% CXX - C++ compiler -%% CFLAGS - C compiler -%% CXXFLAGS - C++ compiler -%% LDFLAGS - Link flags -%% ERL_CFLAGS - default -I paths for erts and ei -%% ERL_LDFLAGS - default -L and -lerl_interface -lei -%% DRV_CFLAGS - flags that will be used for compiling -%% DRV_LDFLAGS - flags that will be used for linking -%% EXE_CFLAGS - flags that will be used for compiling -%% EXE_LDFLAGS - flags that will be used for linking -%% ERL_EI_LIBDIR - ei library directory -%% DRV_CXX_TEMPLATE - C++ command template -%% DRV_CC_TEMPLATE - C command template -%% DRV_LINK_TEMPLATE - Linker command template -%% EXE_CXX_TEMPLATE - C++ command template -%% EXE_CC_TEMPLATE - C command template -%% EXE_LINK_TEMPLATE - Linker command template -%% PORT_IN_FILES - contains a space separated list of input -%% file(s), (used in command template) -%% PORT_OUT_FILE - contains the output filename (used in -%% command template) -%% -%% Note that if you wish to extend (vs. replace) these variables, -%% you MUST include a shell-style reference in your definition. -%% e.g. to extend CFLAGS, do something like: -%% -%% {port_env, [{"CFLAGS", "$CFLAGS -MyOtherOptions"}]} -%% -%% It is also possible to specify platform specific options -%% by specifying a triplet where the first string is a regex -%% that is checked against Erlang's system architecture string. -%% e.g. to specify a CFLAG that only applies to x86_64 on linux -%% do: -%% -%% {port_env, [{"x86_64.*-linux", "CFLAGS", -%% "$CFLAGS -X86Options"}]} -%% - --record(spec, {type::'drv' | 'exe', - target::file:filename(), - sources = [] :: [file:filename(), ...], - objects = [] :: [file:filename(), ...], - opts = [] ::list() | []}). - -compile(Config, AppFile) -> - case get_specs(Config, AppFile) of - [] -> - ok; - Specs -> - SharedEnv = rebar_config:get_env(Config, rebar_deps) ++ - rebar_config:get_env(Config, ?MODULE), - - %% Compile each of the sources - NewBins = compile_sources(Config, Specs, SharedEnv), - - %% Make sure that the target directories exist - ?INFO("Using specs ~p\n", [Specs]), - lists:foreach(fun(#spec{target=Target}) -> - ok = filelib:ensure_dir(Target) - end, Specs), - - %% Only relink if necessary, given the Target - %% and list of new binaries - lists:foreach( - fun(#spec{target=Target, objects=Bins, opts=Opts}) -> - AllBins = [sets:from_list(Bins), - sets:from_list(NewBins)], - Intersection = sets:intersection(AllBins), - case needs_link(Target, sets:to_list(Intersection)) of - true -> - LinkTemplate = select_link_template(Target), - Env = proplists:get_value(env, Opts, SharedEnv), - Cmd = expand_command(LinkTemplate, Env, - string:join(Bins, " "), - Target), - rebar_utils:sh(Cmd, [{env, Env}]); - false -> - ?INFO("Skipping relink of ~s\n", [Target]), - ok - end - end, Specs) - end. - -clean(Config, AppFile) -> - case get_specs(Config, AppFile) of - [] -> - ok; - Specs -> - lists:foreach(fun(#spec{target=Target, objects=Objects}) -> - rebar_file_utils:delete_each([Target]), - rebar_file_utils:delete_each(Objects) - end, Specs) - end, - ok. - -setup_env(Config) -> - setup_env(Config, []). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - info_help("Build port sources"); -info(help, clean) -> - info_help("Delete port build results"). - -info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " ~p~n" - " ~p~n", - [ - Description, - {port_env, [{"CFLAGS", "$CFLAGS -Ifoo"}, - {"freebsd", "LDFLAGS", "$LDFLAGS -lfoo"}]}, - {port_specs, [{"priv/so_name.so", ["c_src/*.c"]}, - {"linux", "priv/hello_linux", ["c_src/hello_linux.c"]}, - {"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}]} - ]). - -setup_env(Config, ExtraEnv) -> - %% Extract environment values from the config (if specified) and - %% merge with the default for this operating system. This enables - %% max flexibility for users. - DefaultEnv = filter_env(default_env(), []), - - %% Get any port-specific envs; use port_env first and then fallback - %% to port_envs for compatibility - RawPortEnv = rebar_config:get_list(Config, port_env, - rebar_config:get_list(Config, port_envs, [])), - - PortEnv = filter_env(RawPortEnv, []), - Defines = get_defines(Config), - OverrideEnv = Defines ++ PortEnv ++ filter_env(ExtraEnv, []), - RawEnv = apply_defaults(os_env(), DefaultEnv) ++ OverrideEnv, - expand_vars_loop(merge_each_var(RawEnv, [])). - -get_defines(Config) -> - RawDefines = rebar_config:get_xconf(Config, defines, []), - Defines = string:join(["-D" ++ D || D <- RawDefines], " "), - [{"ERL_CFLAGS", "$ERL_CFLAGS " ++ Defines}]. - -replace_extension(File, NewExt) -> - OldExt = filename:extension(File), - replace_extension(File, OldExt, NewExt). - -replace_extension(File, OldExt, NewExt) -> - filename:rootname(File, OldExt) ++ NewExt. - -%% -%% == compile and link == -%% - -compile_sources(Config, Specs, SharedEnv) -> - lists:foldl( - fun(#spec{sources=Sources, type=Type, opts=Opts}, NewBins) -> - Env = proplists:get_value(env, Opts, SharedEnv), - compile_each(Config, Sources, Type, Env, NewBins) - end, [], Specs). - -compile_each(_Config, [], _Type, _Env, NewBins) -> - lists:reverse(NewBins); -compile_each(Config, [Source | Rest], Type, Env, NewBins) -> - Ext = filename:extension(Source), - Bin = replace_extension(Source, Ext, ".o"), - case needs_compile(Source, Bin) of - true -> - Template = select_compile_template(Type, compiler(Ext)), - Cmd = expand_command(Template, Env, Source, Bin), - ShOpts = [{env, Env}, return_on_error, {use_stdout, false}], - exec_compiler(Config, Source, Cmd, ShOpts), - compile_each(Config, Rest, Type, Env, [Bin | NewBins]); - false -> - ?INFO("Skipping ~s\n", [Source]), - compile_each(Config, Rest, Type, Env, NewBins) - end. - -exec_compiler(Config, Source, Cmd, ShOpts) -> - case rebar_utils:sh(Cmd, ShOpts) of - {error, {_RC, RawError}} -> - AbsSource = case rebar_utils:processing_base_dir(Config) of - true -> - Source; - false -> - filename:absname(Source) - end, - ?CONSOLE("Compiling ~s\n", [AbsSource]), - Error = re:replace(RawError, Source, AbsSource, - [{return, list}, global]), - ?CONSOLE("~s", [Error]), - ?FAIL; - {ok, Output} -> - ?CONSOLE("Compiling ~s\n", [Source]), - ?CONSOLE("~s", [Output]) - end. - -needs_compile(Source, Bin) -> - %% TODO: Generate depends using gcc -MM so we can also - %% check for include changes - filelib:last_modified(Bin) < filelib:last_modified(Source). - -needs_link(SoName, []) -> - filelib:last_modified(SoName) == 0; -needs_link(SoName, NewBins) -> - MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]), - case filelib:last_modified(SoName) of - 0 -> - ?DEBUG("Last mod is 0 on ~s\n", [SoName]), - true; - Other -> - ?DEBUG("Checking ~p >= ~p\n", [MaxLastMod, Other]), - MaxLastMod >= Other - end. - -%% -%% == port_specs == -%% - -get_specs(Config, AppFile) -> - Specs = case rebar_config:get_local(Config, port_specs, []) of - [] -> - %% No spec provided. Construct a spec - %% from old-school so_name and sources - [port_spec_from_legacy(Config, AppFile)]; - PortSpecs -> - Filtered = filter_port_specs(PortSpecs), - OsType = os:type(), - [get_port_spec(Config, OsType, Spec) || Spec <- Filtered] - end, - [S || S <- Specs, S#spec.sources /= []]. - -port_spec_from_legacy(Config, AppFile) -> - %% Get the target from the so_name variable - Target = case rebar_config:get(Config, so_name, undefined) of - undefined -> - %% Generate a sensible default from app file - {_, AppName} = rebar_app_utils:app_name(Config, AppFile), - filename:join("priv", - lists:concat([AppName, "_drv.so"])); - AName -> - %% Old form is available -- use it - filename:join("priv", AName) - end, - %% Get the list of source files from port_sources - Sources = port_sources(rebar_config:get_list(Config, port_sources, - ["c_src/*.c"])), - #spec { type = target_type(Target), - target = maybe_switch_extension(os:type(), Target), - sources = Sources, - objects = port_objects(Sources) }. - -filter_port_specs(Specs) -> - [S || S <- Specs, filter_port_spec(S)]. - -filter_port_spec({ArchRegex, _, _, _}) -> - rebar_utils:is_arch(ArchRegex); -filter_port_spec({ArchRegex, _, _}) -> - rebar_utils:is_arch(ArchRegex); -filter_port_spec({_, _}) -> - true. - -get_port_spec(Config, OsType, {Target, Sources}) -> - get_port_spec(Config, OsType, {undefined, Target, Sources, []}); -get_port_spec(Config, OsType, {Arch, Target, Sources}) -> - get_port_spec(Config, OsType, {Arch, Target, Sources, []}); -get_port_spec(Config, OsType, {_Arch, Target, Sources, Opts}) -> - SourceFiles = port_sources(Sources), - ObjectFiles = port_objects(SourceFiles), - #spec{type=target_type(Target), - target=maybe_switch_extension(OsType, Target), - sources=SourceFiles, - objects=ObjectFiles, - opts=port_opts(Config, Opts)}. - -port_sources(Sources) -> - lists:flatmap(fun filelib:wildcard/1, Sources). - -port_objects(SourceFiles) -> - [replace_extension(O, ".o") || O <- SourceFiles]. - -port_opts(Config, Opts) -> - [port_opt(Config, O) || O <- Opts]. - -port_opt(Config, {env, Env}) -> - {env, setup_env(Config, Env)}; -port_opt(_Config, Opt) -> - Opt. - -maybe_switch_extension({win32, nt}, Target) -> - switch_to_dll_or_exe(Target); -maybe_switch_extension(_OsType, Target) -> - Target. - -switch_to_dll_or_exe(Target) -> - case filename:extension(Target) of - ".so" -> filename:rootname(Target, ".so") ++ ".dll"; - [] -> Target ++ ".exe"; - _Other -> Target - end. - -%% -%% == port_env == -%% - -%% -%% Choose a compiler variable, based on a provided extension -%% -compiler(".cc") -> "$CXX"; -compiler(".cp") -> "$CXX"; -compiler(".cxx") -> "$CXX"; -compiler(".cpp") -> "$CXX"; -compiler(".CPP") -> "$CXX"; -compiler(".c++") -> "$CXX"; -compiler(".C") -> "$CXX"; -compiler(_) -> "$CC". - -%% -%% Given a list of {Key, Value} variables, and another list of default -%% {Key, Value} variables, return a merged list where the rule is if the -%% default is expandable expand it with the value of the variable list, -%% otherwise just return the value of the variable. -%% -apply_defaults(Vars, Defaults) -> - dict:to_list( - dict:merge(fun(Key, VarValue, DefaultValue) -> - case is_expandable(DefaultValue) of - true -> - rebar_utils:expand_env_variable(DefaultValue, - Key, - VarValue); - false -> VarValue - end - end, - dict:from_list(Vars), - dict:from_list(Defaults))). - -%% -%% Given a list of {Key, Value} environment variables, where Key may be defined -%% multiple times, walk the list and expand each self-reference so that we -%% end with a list of each variable singly-defined. -%% -merge_each_var([], Vars) -> - Vars; -merge_each_var([{Key, Value} | Rest], Vars) -> - Evalue = case orddict:find(Key, Vars) of - error -> - %% Nothing yet defined for this key/value. - %% Expand any self-references as blank. - rebar_utils:expand_env_variable(Value, Key, ""); - {ok, Value0} -> - %% Use previous definition in expansion - rebar_utils:expand_env_variable(Value, Key, Value0) - end, - merge_each_var(Rest, orddict:store(Key, Evalue, Vars)). - -%% -%% Give a unique list of {Key, Value} environment variables, expand each one -%% for every other key until no further expansions are possible. -%% -expand_vars_loop(Vars) -> - expand_vars_loop(Vars, [], dict:from_list(Vars), 10). - -expand_vars_loop(_Pending, _Recurse, _Vars, 0) -> - ?ABORT("Max. expansion reached for ENV vars!\n", []); -expand_vars_loop([], [], Vars, _Count) -> - lists:keysort(1, dict:to_list(Vars)); -expand_vars_loop([], Recurse, Vars, Count) -> - expand_vars_loop(Recurse, [], Vars, Count-1); -expand_vars_loop([{K, V} | Rest], Recurse, Vars, Count) -> - %% Identify the variables that need expansion in this value - ReOpts = [global, {capture, all_but_first, list}, unicode], - case re:run(V, "\\\${?(\\w+)}?", ReOpts) of - {match, Matches} -> - %% Identify the unique variables that need to be expanded - UniqueMatches = lists:usort([M || [M] <- Matches]), - - %% For each variable, expand it and return the final - %% value. Note that if we have a bunch of unresolvable - %% variables, nothing happens and we don't bother - %% attempting further expansion - case expand_keys_in_value(UniqueMatches, V, Vars) of - V -> - %% No change after expansion; move along - expand_vars_loop(Rest, Recurse, Vars, Count); - Expanded -> - %% Some expansion occurred; move to next k/v but - %% revisit this value in the next loop to check - %% for further expansion - NewVars = dict:store(K, Expanded, Vars), - expand_vars_loop(Rest, [{K, Expanded} | Recurse], - NewVars, Count) - end; - - nomatch -> - %% No values in this variable need expansion; move along - expand_vars_loop(Rest, Recurse, Vars, Count) - end. - -expand_keys_in_value([], Value, _Vars) -> - Value; -expand_keys_in_value([Key | Rest], Value, Vars) -> - NewValue = case dict:find(Key, Vars) of - {ok, KValue} -> - rebar_utils:expand_env_variable(Value, Key, KValue); - error -> - Value - end, - expand_keys_in_value(Rest, NewValue, Vars). - -expand_command(TmplName, Env, InFiles, OutFile) -> - Cmd0 = proplists:get_value(TmplName, Env), - Cmd1 = rebar_utils:expand_env_variable(Cmd0, "PORT_IN_FILES", InFiles), - rebar_utils:expand_env_variable(Cmd1, "PORT_OUT_FILE", OutFile). - -%% -%% Given a string, determine if it is expandable -%% -is_expandable(InStr) -> - case re:run(InStr,"\\\$",[{capture,none}]) of - match -> true; - nomatch -> false - end. - -%% -%% Filter a list of env vars such that only those which match the provided -%% architecture regex (or do not have a regex) are returned. -%% -filter_env([], Acc) -> - lists:reverse(Acc); -filter_env([{ArchRegex, Key, Value} | Rest], Acc) -> - case rebar_utils:is_arch(ArchRegex) of - true -> - filter_env(Rest, [{Key, Value} | Acc]); - false -> - filter_env(Rest, Acc) - end; -filter_env([{Key, Value} | Rest], Acc) -> - filter_env(Rest, [{Key, Value} | Acc]). - -erts_dir() -> - lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]). - -os_env() -> - ReOpts = [{return, list}, {parts, 2}, unicode], - Os = [list_to_tuple(re:split(S, "=", ReOpts)) || - S <- lists:filter(fun discard_deps_vars/1, os:getenv())], - %% Drop variables without a name (win32) - [T1 || {K, _V} = T1 <- Os, K =/= []]. - -%% -%% To avoid having multiple repetitions of the same environment variables -%% (ERL_LIBS), avoid exporting any variables that may cause conflict with -%% those exported by the rebar_deps module (ERL_LIBS, REBAR_DEPS_DIR) -%% -discard_deps_vars("ERL_LIBS=" ++ _Value) -> false; -discard_deps_vars("REBAR_DEPS_DIR=" ++ _Value) -> false; -discard_deps_vars(_Var) -> true. - -select_compile_template(drv, Compiler) -> - select_compile_drv_template(Compiler); -select_compile_template(exe, Compiler) -> - select_compile_exe_template(Compiler). - -select_compile_drv_template("$CC") -> "DRV_CC_TEMPLATE"; -select_compile_drv_template("$CXX") -> "DRV_CXX_TEMPLATE". - -select_compile_exe_template("$CC") -> "EXE_CC_TEMPLATE"; -select_compile_exe_template("$CXX") -> "EXE_CXX_TEMPLATE". - -select_link_template(Target) -> - case target_type(Target) of - drv -> "DRV_LINK_TEMPLATE"; - exe -> "EXE_LINK_TEMPLATE" - end. - -target_type(Target) -> target_type1(filename:extension(Target)). - -target_type1(".so") -> drv; -target_type1(".dll") -> drv; -target_type1("") -> exe; -target_type1(".exe") -> exe. - -erl_interface_dir(Subdir) -> - case code:lib_dir(erl_interface, Subdir) of - {error, bad_name} -> - throw({error, {erl_interface,Subdir,"code:lib_dir(erl_interface)" - "is unable to find the erl_interface library."}}); - Dir -> Dir - end. - -default_env() -> - [ - {"CC" , "cc"}, - {"CXX", "c++"}, - {"DRV_CXX_TEMPLATE", - "$CXX -c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, - {"DRV_CC_TEMPLATE", - "$CC -c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, - {"DRV_LINK_TEMPLATE", - "$CC $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE"}, - {"EXE_CXX_TEMPLATE", - "$CXX -c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, - {"EXE_CC_TEMPLATE", - "$CC -c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, - {"EXE_LINK_TEMPLATE", - "$CC $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE"}, - {"DRV_CFLAGS" , "-g -Wall -fPIC $ERL_CFLAGS"}, - {"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"}, - {"EXE_CFLAGS" , "-g -Wall -fPIC $ERL_CFLAGS"}, - {"EXE_LDFLAGS", "$ERL_LDFLAGS"}, - - {"ERL_CFLAGS", lists:concat([" -I\"", erl_interface_dir(include), - "\" -I\"", filename:join(erts_dir(), "include"), - "\" "])}, - {"ERL_EI_LIBDIR", lists:concat(["\"", erl_interface_dir(lib), "\""])}, - {"ERL_LDFLAGS" , " -L$ERL_EI_LIBDIR -lerl_interface -lei"}, - {"ERLANG_ARCH" , rebar_utils:wordsize()}, - {"ERLANG_TARGET", rebar_utils:get_arch()}, - - {"darwin", "DRV_LDFLAGS", - "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, - - %% Solaris specific flags - {"solaris.*-64$", "CFLAGS", "-D_REENTRANT -m64 $CFLAGS"}, - {"solaris.*-64$", "CXXFLAGS", "-D_REENTRANT -m64 $CXXFLAGS"}, - {"solaris.*-64$", "LDFLAGS", "-m64 $LDFLAGS"}, - - %% OS X Leopard flags for 64-bit - {"darwin9.*-64$", "CFLAGS", "-m64 $CFLAGS"}, - {"darwin9.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, - {"darwin9.*-64$", "LDFLAGS", "-arch x86_64 $LDFLAGS"}, - - %% OS X Snow Leopard, Lion, and Mountain Lion flags for 32-bit - {"darwin1[0-2].*-32", "CFLAGS", "-m32 $CFLAGS"}, - {"darwin1[0-2].*-32", "CXXFLAGS", "-m32 $CXXFLAGS"}, - {"darwin1[0-2].*-32", "LDFLAGS", "-arch i386 $LDFLAGS"}, - - %% Windows specific flags - %% add MS Visual C++ support to rebar on Windows - {"win32", "CC", "cl.exe"}, - {"win32", "CXX", "cl.exe"}, - {"win32", "LINKER", "link.exe"}, - {"win32", "DRV_CXX_TEMPLATE", - %% DRV_* and EXE_* Templates are identical - "$CXX /c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, - {"win32", "DRV_CC_TEMPLATE", - "$CC /c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, - {"win32", "DRV_LINK_TEMPLATE", - "$LINKER $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS /OUT:$PORT_OUT_FILE"}, - %% DRV_* and EXE_* Templates are identical - {"win32", "EXE_CXX_TEMPLATE", - "$CXX /c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, - {"win32", "EXE_CC_TEMPLATE", - "$CC /c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, - {"win32", "EXE_LINK_TEMPLATE", - "$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"}, - %% ERL_CFLAGS are ok as -I even though strictly it should be /I - {"win32", "ERL_LDFLAGS", " /LIBPATH:$ERL_EI_LIBDIR erl_interface.lib ei.lib"}, - {"win32", "DRV_CFLAGS", "/Zi /Wall $ERL_CFLAGS"}, - {"win32", "DRV_LDFLAGS", "/DLL $ERL_LDFLAGS"} - ]. diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl deleted file mode 100644 index e89c700..0000000 --- a/src/rebar_protobuffs_compiler.erl +++ /dev/null @@ -1,153 +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_protobuffs_compiler). - --export([compile/2, - clean/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - -compile(Config, _AppFile) -> - case rebar_utils:find_files("src", "^[^._].*\\.proto$") of - [] -> - ok; - FoundFiles -> - %% Check for protobuffs library -- if it's not present, fail - %% since we have.proto files that need building - case protobuffs_is_present() of - true -> - %% Build a list of output files - { Proto, Beam, Hrl } - Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} || - Proto <- FoundFiles], - - %% Compile each proto file - compile_each(Config, Targets); - false -> - ?ERROR("Protobuffs library not present in code path!\n", - []), - ?FAIL - end - end. - -clean(_Config, _AppFile) -> - %% Get a list of generated .beam and .hrl files and then delete them - Protos = rebar_utils:find_files("src", ".*\\.proto$"), - BeamFiles = [fq_beam_file(F) || F <- Protos], - HrlFiles = [fq_hrl_file(F) || F <- Protos], - Targets = BeamFiles ++ HrlFiles, - case Targets of - [] -> - ok; - _ -> - delete_each(Targets) - end. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, compile) -> - info_help("Build Protobuffs (*.proto) sources"); -info(help, clean) -> - info_help("Delete Protobuffs (*.proto) build results"). - -info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " erl_opts is passed as compile_flags to " - "protobuffs_compile:scan_file/2~n", - [Description]). - -protobuffs_is_present() -> - code:which(protobuffs_compile) =/= non_existing. - -beam_file(Proto) -> - filename:basename(Proto, ".proto") ++ "_pb.beam". - -hrl_file(Proto) -> - filename:basename(Proto, ".proto") ++ "_pb.hrl". - -fq_beam_file(Proto) -> - filename:join(["ebin", filename:basename(Proto, ".proto") ++ "_pb.beam"]). - -fq_hrl_file(Proto) -> - filename:join(["include", filename:basename(Proto, ".proto") ++ "_pb.hrl"]). - -needs_compile(Proto, Beam) -> - ActualBeam = filename:join(["ebin", filename:basename(Beam)]), - filelib:last_modified(ActualBeam) < filelib:last_modified(Proto). - -compile_each(_, []) -> - ok; -compile_each(Config, [{Proto, Beam, Hrl} | Rest]) -> - case needs_compile(Proto, Beam) of - true -> - ?CONSOLE("Compiling ~s\n", [Proto]), - ErlOpts = rebar_utils:erl_opts(Config), - case protobuffs_compile:scan_file(Proto, - [{compile_flags,ErlOpts}]) of - ok -> - %% Compilation worked, but we need to move the - %% beam and .hrl file into the ebin/ and include/ - %% directories respectively - %% TODO: Protobuffs really needs to be better about this - ok = filelib:ensure_dir(filename:join("ebin","dummy")), - ok = rebar_file_utils:mv(Beam, "ebin"), - ok = filelib:ensure_dir(filename:join("include", Hrl)), - ok = rebar_file_utils:mv(Hrl, "include"), - ok; - Other -> - ?ERROR("Protobuffs compile of ~s failed: ~p\n", - [Proto, Other]), - ?FAIL - end; - false -> - ok - end, - compile_each(Config, Rest). - -delete_each([]) -> - ok; -delete_each([File | Rest]) -> - case file:delete(File) of - ok -> - ok; - {error, enoent} -> - ok; - {error, Reason} -> - ?ERROR("Failed to delete ~s: ~p\n", [File, Reason]) - end, - delete_each(Rest). diff --git a/src/rebar_provider.erl b/src/rebar_provider.erl new file mode 100644 index 0000000..e8986f2 --- /dev/null +++ b/src/rebar_provider.erl @@ -0,0 +1,139 @@ +-module(rebar_provider). + +%% API +-export([new/2, + do/2, + impl/1, + get_provider/2, + get_target_providers/2, + 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_config:config()) -> {ok, rebar_config:config()} | relx:error(). +-callback do(rebar_config:config()) -> {ok, rebar_config:config()} | 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_config:config()) -> + {ok, rebar_config:config()} | relx:error(). +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_config:config()) -> + {ok, rebar_config:config()} | relx:error(). +do(Provider, State) -> + (Provider#provider.provider_impl):do(State). + +%%% @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. + +%% @doc print the provider module name +%% +%% @param T - The provider +%% @return An iolist describing the provider +-spec format(t()) -> iolist(). +format(#provider{provider_impl=Mod}) -> + erlang:atom_to_list(Mod). + +get_target_providers(Targets, State) -> + Providers = rebar_config:providers(State), + TargetProviders = lists:filter(fun(#provider{provides=T}) -> + lists:member(T, Targets) + 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. diff --git a/src/rebar_prv_app_builder.erl b/src/rebar_prv_app_builder.erl new file mode 100644 index 0000000..6d1498d --- /dev/null +++ b/src/rebar_prv_app_builder.erl @@ -0,0 +1,95 @@ +-module(rebar_prv_app_builder). + +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, app_builder). +-define(DEPS, [deps]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = build, + bare = false, + deps = ?DEPS, + example = "rebar build", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + Deps = rebar_config:deps_to_build(Config), + Apps = rebar_config:apps_to_build(Config), + Config1 = + lists:foldl(fun(AppInfo, ConfigAcc) -> + ?CONSOLE("Building ~p version ~p~n", [rebar_app_info:name(AppInfo) + ,rebar_app_info:original_vsn(AppInfo)]), + {_AppInfo1, ConfigAcc1} = build(ConfigAcc, AppInfo), + ConfigAcc + end, Config, Deps++Apps), + Graph = construct_graph(Config), + Goals = rebar_config:goals(Config1), + {ok, Solve} = rlx_depsolver:solve(Graph, Goals), + + DepsDir = get_deps_dir(Config1), + LockDeps = lists:map(fun({Name, _, Source}) -> + Dir = get_deps_dir(DepsDir, Name), + {Name, Vsn} = lists:keyfind(Name, 1, Solve), + rebar_fetch:new(Dir, Name, format_vsn(Vsn), Source) + end, rebar_config:deps(Config)), + ok = file:write_file("./rebar.lock", io_lib:format("~p.~n", [LockDeps])), + {ok, Config1}. + +build(Config, AppInfo) -> + {ok, AppInfo1} = rebar_otp_app:compile(Config, AppInfo), + Config1 = rebar_config:replace_app(Config, rebar_app_info:name(AppInfo1), AppInfo1), + rebar_erlc_compiler:compile(Config, rebar_app_info:dir(AppInfo1)), + {AppInfo1, Config1}. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +construct_graph(Config) -> + LibDirs = rebar_config:get_local(Config, lib_dirs, ["apps", "libs", "."]), + DepsDir = rebar_deps:get_deps_dir(Config), + Graph = rlx_depsolver:new_graph(), + Apps = rebar_app_discover:find_apps([DepsDir | LibDirs]), + lists:foldl(fun(AppInfo, GraphAcc) -> + Name = rebar_app_info:name(AppInfo), + Vsn = rebar_app_info:original_vsn(AppInfo), + C = rebar_config:new2(rebar_config:new(), rebar_app_info:dir(AppInfo)), + LocalDeps = rebar_config:get_local(C, deps, []), + PkgDeps = lists:map(fun({A, "", _}) -> + A; + ({A, ".*", _}) -> + A; + ({A, V, _}) -> + {A, V} + end, LocalDeps), + rlx_depsolver:add_package_version(GraphAcc + ,Name + ,Vsn + ,PkgDeps) + end, Graph, Apps). + +get_deps_dir(Config) -> + BaseDir = rebar_utils:base_dir(Config), + get_deps_dir(BaseDir, deps). + +get_deps_dir(DepsDir, App) -> + filename:join(DepsDir, atom_to_list(App)). + +format_vsn(Vsn) -> + binary_to_list(iolist_to_binary(ec_semver:format(Vsn))). diff --git a/src/rebar_prv_release.erl b/src/rebar_prv_release.erl new file mode 100644 index 0000000..3edf2eb --- /dev/null +++ b/src/rebar_prv_release.erl @@ -0,0 +1,37 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_release). + +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, release). +-define(DEPS, [app_builder]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = release, + bare = false, + deps = ?DEPS, + example = "rebar release", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + RelxConfig = rebar_config:get_local(Config, relx, []), + relx:main("release"), + {ok, Config}. diff --git a/src/rebar_prv_tar.erl b/src/rebar_prv_tar.erl new file mode 100644 index 0000000..d45c70d --- /dev/null +++ b/src/rebar_prv_tar.erl @@ -0,0 +1,37 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(rebar_prv_tar). + +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + +-include("rebar.hrl"). + +-define(PROVIDER, tar). +-define(DEPS, []). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = tar, + bare = false, + deps = ?DEPS, + example = "rebar tar", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + RelxConfig = rebar_config:get_local(Config, relx, []), + relx:main("release tar"), + {ok, Config}. diff --git a/src/rebar_rel_utils.erl b/src/rebar_rel_utils.erl deleted file mode 100644 index 5d99948..0000000 --- a/src/rebar_rel_utils.erl +++ /dev/null @@ -1,246 +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_rel_utils). - --export([is_rel_dir/0, - is_rel_dir/1, - get_reltool_release_info/1, - get_rel_release_info/1, - get_rel_release_info/2, - get_rel_apps/1, - get_rel_apps/2, - get_previous_release_path/1, - get_rel_file_path/2, - load_config/2, - get_sys_tuple/1, - get_excl_lib_tuple/1, - get_target_dir/2, - get_root_dir/2, - get_target_parent_dir/2]). - --include("rebar.hrl"). - -is_rel_dir() -> - is_rel_dir(rebar_utils:get_cwd()). - -is_rel_dir(Dir) -> - Fname = filename:join([Dir, "reltool.config"]), - Scriptname = Fname ++ ".script", - Res = case filelib:is_regular(Scriptname) of - true -> - {true, Scriptname}; - false -> - case filelib:is_regular(Fname) of - true -> - {true, Fname}; - false -> - false - end - end, - ?DEBUG("is_rel_dir(~s) -> ~p~n", [Dir, Res]), - Res. - -%% Get release name and version from a reltool.config -get_reltool_release_info([{sys, Config}| _]) -> - {rel, Name, Ver, _} = proplists:lookup(rel, Config), - {Name, Ver}; -get_reltool_release_info(ReltoolFile) when is_list(ReltoolFile) -> - case file:consult(ReltoolFile) of - {ok, ReltoolConfig} -> - get_reltool_release_info(ReltoolConfig); - _ -> - ?ABORT("Failed to parse ~s~n", [ReltoolFile]) - end. - -%% Get release name and version from a rel file -get_rel_release_info(RelFile) -> - case file:consult(RelFile) of - {ok, [{release, {Name, Ver}, _, _}]} -> - {Name, Ver}; - _ -> - ?ABORT("Failed to parse ~s~n", [RelFile]) - end. - -%% Get release name and version from a name and a path -get_rel_release_info(Name, Path) -> - RelPath = get_rel_file_path(Name, Path), - get_rel_release_info(RelPath). - -%% Get list of apps included in a release from a rel file -get_rel_apps(RelFile) -> - case file:consult(RelFile) of - {ok, [{release, _, _, Apps}]} -> - make_proplist(Apps, []); - _ -> - ?ABORT("Failed to parse ~s~n", [RelFile]) - end. - -%% Get list of apps included in a release from a name and a path -get_rel_apps(Name, Path) -> - RelPath = get_rel_file_path(Name, Path), - get_rel_apps(RelPath). - -%% Get rel file path from name and path -get_rel_file_path(Name, Path) -> - [RelFile] = filelib:wildcard(filename:join([Path, "releases", "*", - Name ++ ".rel"])), - RelFile. - -%% Get the previous release path from a global variable -get_previous_release_path(Config) -> - case rebar_config:get_global(Config, previous_release, false) of - false -> - ?ABORT("previous_release=PATH is required to " - "create upgrade package~n", []); - OldVerPath -> - OldVerPath - end. - -%% -%% Load terms from reltool.config -%% -load_config(Config, ReltoolFile) -> - case rebar_config:consult_file(ReltoolFile) of - {ok, Terms} -> - expand_version(Config, Terms, filename:dirname(ReltoolFile)); - Other -> - ?ABORT("Failed to load expected config from ~s: ~p\n", - [ReltoolFile, Other]) - end. - -%% -%% Look for the {sys, [...]} tuple in the reltool.config file. -%% Without this present, we can't run reltool. -%% -get_sys_tuple(ReltoolConfig) -> - case lists:keyfind(sys, 1, ReltoolConfig) of - {sys, _} = SysTuple -> - SysTuple; - false -> - ?ABORT("Failed to find {sys, [...]} tuple in reltool.config.", []) - end. - -%% -%% Look for the {excl_lib, ...} tuple in sys tuple of the reltool.config file. -%% Without this present, return false. -%% -get_excl_lib_tuple(ReltoolConfig) -> - lists:keyfind(excl_lib, 1, element(2, get_sys_tuple(ReltoolConfig))). - -%% -%% Look for {target_dir, TargetDir} in the reltool config file; if none is -%% found, use the name of the release as the default target directory. -%% -get_target_dir(Config, ReltoolConfig) -> - case rebar_config:get_global(Config, target_dir, undefined) of - undefined -> - case lists:keyfind(target_dir, 1, ReltoolConfig) of - {target_dir, TargetDir} -> - filename:absname(TargetDir); - false -> - {sys, SysInfo} = get_sys_tuple(ReltoolConfig), - case lists:keyfind(rel, 1, SysInfo) of - {rel, Name, _Vsn, _Apps} -> - filename:absname(Name); - false -> - filename:absname("target") - end - end; - TargetDir -> - filename:absname(TargetDir) - end. - -get_target_parent_dir(Config, ReltoolConfig) -> - TargetDir = get_target_dir(Config, ReltoolConfig), - case lists:reverse(tl(lists:reverse(filename:split(TargetDir)))) of - [] -> "."; - Components -> filename:join(Components) - end. - -%% -%% Look for root_dir in sys tuple and command line; fall back to -%% code:root_dir(). -%% -get_root_dir(Config, ReltoolConfig) -> - {sys, SysInfo} = get_sys_tuple(ReltoolConfig), - SysRootDirTuple = lists:keyfind(root_dir, 1, SysInfo), - CmdRootDir = rebar_config:get_global(Config, root_dir, undefined), - case {SysRootDirTuple, CmdRootDir} of - %% root_dir in sys typle and no root_dir on cmd-line - {{root_dir, SysRootDir}, undefined} -> - SysRootDir; - %% root_dir in sys typle and also root_dir on cmd-line - {{root_dir, SysRootDir}, CmdRootDir} when CmdRootDir =/= undefined -> - case string:equal(SysRootDir, CmdRootDir) of - true -> - ok; - false -> - ?WARN("overriding reltool.config root_dir with " - "different command line root_dir~n", []) - end, - CmdRootDir; - %% no root_dir in sys typle and no root_dir on cmd-line - {false, undefined} -> - code:root_dir(); - %% no root_dir in sys tuple but root_dir on cmd-line - {false, CmdRootDir} when CmdRootDir =/= undefined -> - CmdRootDir - end. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -make_proplist([{_,_}=H|T], Acc) -> - make_proplist(T, [H|Acc]); -make_proplist([H|T], Acc) -> - App = element(1, H), - Ver = element(2, H), - make_proplist(T, [{App,Ver}|Acc]); -make_proplist([], Acc) -> - Acc. - -expand_version(Config, ReltoolConfig, Dir) -> - case lists:keyfind(sys, 1, ReltoolConfig) of - {sys, Sys} -> - {Config1, Rels} = - lists:foldl( - fun(Term, {C, R}) -> - {C1, Rel} = expand_rel_version(C, Term, Dir), - {C1, [Rel|R]} - end, {Config, []}, Sys), - ExpandedSys = {sys, lists:reverse(Rels)}, - {Config1, lists:keyreplace(sys, 1, ReltoolConfig, ExpandedSys)}; - _ -> - {Config, ReltoolConfig} - end. - -expand_rel_version(Config, {rel, Name, Version, Apps}, Dir) -> - {NewConfig, VsnString} = rebar_utils:vcs_vsn(Config, Version, Dir), - {NewConfig, {rel, Name, VsnString, Apps}}; -expand_rel_version(Config, Other, _Dir) -> - {Config, Other}. diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl deleted file mode 100644 index fdaa7e0..0000000 --- a/src/rebar_reltool.erl +++ /dev/null @@ -1,425 +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_reltool). - --export([generate/2, - overlay/2, - clean/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). --include_lib("kernel/include/file.hrl"). - -%% =================================================================== -%% Public API -%% =================================================================== - -generate(Config0, ReltoolFile) -> - %% Make sure we have decent version of reltool available - check_vsn(), - - %% Load the reltool configuration from the file - {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile), - - Sys = rebar_rel_utils:get_sys_tuple(ReltoolConfig), - - %% Spin up reltool server and load our config into it - {ok, Server} = reltool:start_server([Sys]), - - %% Do some validation of the reltool configuration; error messages out of - %% reltool are still pretty cryptic - validate_rel_apps(Server, Sys), - - %% Finally, run reltool - case catch(run_reltool(Server, Config, ReltoolConfig)) of - ok -> - {ok, Config}; - {error, failed} -> - ?FAIL; - Other2 -> - ?ERROR("Unexpected error: ~p\n", [Other2]), - ?FAIL - end. - -overlay(Config, ReltoolFile) -> - %% Load the reltool configuration from the file - {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile), - {process_overlay(Config, ReltoolConfig), Config1}. - -clean(Config, ReltoolFile) -> - {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile), - TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - rebar_file_utils:rm_rf(TargetDir), - rebar_file_utils:delete_each(["reltool.spec"]), - {ok, Config1}. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, generate) -> - info_help("Build release with reltool"); -info(help, clean) -> - info_help("Delete release"); -info(help, overlay) -> - info_help("Run reltool overlays only"). - -info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " ~n" - "Valid reltool.config options:~n" - " {sys, []}~n" - " {target_dir, \"target\"}~n" - " {overlay_vars, \"overlay\"}~n" - " {overlay, []}~n" - "Valid command line options:~n" - " target_dir=target~n" - " overlay_vars=VarsFile~n" - " dump_spec=1 (write reltool target spec to reltool.spec)~n", - [ - Description - ]). - -check_vsn() -> - %% TODO: use application:load and application:get_key once we require - %% R14A or newer. There's no reltool.app before R14A. - case code:lib_dir(reltool) of - {error, bad_name} -> - ?ABORT("Reltool support requires the reltool application " - "to be installed!", []); - Path -> - ReltoolVsn = filename:basename(Path), - case ReltoolVsn < "reltool-0.5.2" of - true -> - ?ABORT("Reltool support requires at least reltool-0.5.2; " - "this VM is using ~s\n", [ReltoolVsn]); - false -> - ok - end - end. - -process_overlay(Config, ReltoolConfig) -> - TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - - {_BootRelName, BootRelVsn} = - rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - - %% Initialize overlay vars with some basics - %% (that can get overwritten) - OverlayVars0 = - dict:from_list([{erts_vsn, "erts-" ++ erlang:system_info(version)}, - {rel_vsn, BootRelVsn}, - {target_dir, TargetDir}, - {hostname, net_adm:localhost()}]), - - %% Load up any variables specified by overlay_vars - OverlayVars1 = overlay_vars(Config, OverlayVars0, ReltoolConfig), - OverlayVars = rebar_templater:resolve_variables(dict:to_list(OverlayVars1), - OverlayVars1), - - %% Finally, overlay the files specified by the overlay section - case overlay_files(ReltoolConfig) of - [] -> - ok; - Overlay -> - execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(), - TargetDir) - end. - -%% -%% Look for overlay_vars file reference. If the user provides an overlay_vars on -%% the command line (i.e. a global), the terms from that file OVERRIDE the one -%% listed in reltool.config. To re-iterate, this means you can specify a -%% variable in the file from reltool.config and then override that value by -%% providing an additional file on the command-line. -%% -overlay_vars(Config, Vars0, ReltoolConfig) -> - BaseVars = load_vars_file([proplists:get_value(overlay_vars, ReltoolConfig)]), - OverlayVars = rebar_config:get_global(Config, overlay_vars, []), - OverrideVars = load_vars_file(string:tokens(OverlayVars, ",")), - M = fun merge_overlay_var/3, - dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars). - -merge_overlay_var(_Key, _Base, Override) -> Override. - -%% -%% If a filename is provided, construct a dict of terms -%% -load_vars_file([undefined]) -> - dict:new(); -load_vars_file([]) -> - dict:new(); -load_vars_file(Files) -> - load_vars_file(Files, dict:new()). - -load_vars_file([], Dict) -> - Dict; -load_vars_file([File | Files], BaseVars) -> - case rebar_config:consult_file(File) of - {ok, Terms} -> - OverrideVars = dict:from_list(Terms), - M = fun merge_overlay_var/3, - load_vars_file(Files, dict:merge(M, BaseVars, OverrideVars)); - {error, Reason} -> - ?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason]) - end. - -validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) -> - case lists:keyfind(rel, 1, ReltoolConfig) of - false -> - ok; - {rel, _Name, _Vsn, Apps} -> - %% Identify all the apps that do NOT exist, based on - %% what's available from the reltool server - Missing = lists:sort( - [App || App <- Apps, - app_exists(App, ReltoolServer) == false]), - case Missing of - [] -> - ok; - _ -> - ?ABORT("Apps in {rel, ...} section not found by " - "reltool: ~p\n", [Missing]) - end; - Rel -> - %% Invalid release format! - ?ABORT("Invalid {rel, ...} section in reltools.config: ~p\n", [Rel]) - end. - -app_exists(App, Server) when is_atom(App) -> - case reltool_server:get_app(Server, App) of - {ok, _} -> - true; - _ -> - false - end; -app_exists(AppTuple, Server) when is_tuple(AppTuple) -> - app_exists(element(1, AppTuple), Server). - -run_reltool(Server, Config, ReltoolConfig) -> - case reltool:get_target_spec(Server) of - {ok, Spec} -> - %% Pull the target dir and make sure it exists - TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - mk_target_dir(Config, TargetDir), - - %% Determine the otp root dir to use - RootDir = rebar_rel_utils:get_root_dir(Config, ReltoolConfig), - - %% Dump the spec, if necessary - dump_spec(Config, Spec), - - %% Have reltool actually run - case reltool:eval_target_spec(Spec, RootDir, TargetDir) of - ok -> - ok; - {error, Reason} -> - ?ABORT("Failed to generate target from spec: ~p\n", - [Reason]) - end, - - {BootRelName, BootRelVsn} = - rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - - ok = create_RELEASES(TargetDir, BootRelName, BootRelVsn), - - process_overlay(Config, ReltoolConfig); - - {error, Reason} -> - ?ABORT("Unable to generate spec: ~s\n", [Reason]) - end. - -mk_target_dir(Config, TargetDir) -> - case filelib:ensure_dir(filename:join(TargetDir, "dummy")) of - ok -> - ok; - {error, eexist} -> - %% Output directory already exists; if force=1, wipe it out - case rebar_config:get_global(Config, force, "0") of - "1" -> - rebar_file_utils:rm_rf(TargetDir), - ok = file:make_dir(TargetDir); - _ -> - ?ERROR("Release target directory ~p already exists!\n", - [TargetDir]), - ?FAIL - end; - {error, Reason} -> - ?ERROR("Failed to make target dir ~p: ~s\n", - [TargetDir, file:format_error(Reason)]), - ?FAIL - end. - -dump_spec(Config, Spec) -> - case rebar_config:get_global(Config, dump_spec, "0") of - "1" -> - SpecBin = list_to_binary(io_lib:print(Spec, 1, 120, -1)), - ok = file:write_file("reltool.spec", SpecBin); - _ -> - ok - end. - - -overlay_files(ReltoolConfig) -> - Original = case lists:keyfind(overlay, 1, ReltoolConfig) of - {overlay, Overlay} when is_list(Overlay) -> - Overlay; - false -> - ?INFO("No {overlay, [...]} found in reltool.config.\n", []), - []; - _ -> - ?ABORT("{overlay, [...]} entry in reltool.config " - "must be a list.\n", []) - end, - SlimAddition = case rebar_rel_utils:get_excl_lib_tuple(ReltoolConfig) of - {excl_lib, otp_root} -> - [{create, "releases/{{rel_vsn}}/runner_script.data", - "slim\n"}]; - false -> - [] - end, - Original ++ SlimAddition. - -%% TODO: Merge functionality here with rebar_templater - -execute_overlay([], _Vars, _BaseDir, _TargetDir) -> - ok; -execute_overlay([{mkdir, Out} | Rest], Vars, BaseDir, TargetDir) -> - OutFile = rebar_templater:render( - filename:join([TargetDir, Out, "dummy"]), Vars), - ok = filelib:ensure_dir(OutFile), - ?DEBUG("Created dir ~s\n", [filename:dirname(OutFile)]), - execute_overlay(Rest, Vars, BaseDir, TargetDir); -execute_overlay([{copy, In} | Rest], _Vars, BaseDir, TargetDir) -> - execute_overlay([{copy, In, ""} | Rest], _Vars, BaseDir, TargetDir); -execute_overlay([{copy, In, Out} | Rest], Vars, BaseDir, TargetDir) -> - InFile = rebar_templater:render(filename:join(BaseDir, In), Vars), - OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars), - case filelib:is_dir(InFile) of - true -> - ok; - false -> - ok = filelib:ensure_dir(OutFile) - end, - rebar_file_utils:cp_r([InFile], OutFile), - execute_overlay(Rest, Vars, BaseDir, TargetDir); -execute_overlay([{template_wildcard, Wildcard, OutDir} | Rest], Vars, - BaseDir, TargetDir) -> - %% Generate a series of {template, In, Out} instructions from the wildcard - %% that will get processed per normal - Ifun = fun(F, Acc0) -> - [{template, F, - filename:join(OutDir, filename:basename(F))} | Acc0] - end, - NewInstrs = lists:foldl(Ifun, Rest, filelib:wildcard(Wildcard, BaseDir)), - case length(NewInstrs) =:= length(Rest) of - true -> - ?WARN("template_wildcard: ~s did not match any files!\n", - [Wildcard]); - false -> - ok - end, - ?DEBUG("template_wildcard: ~s expanded to ~p\n", [Wildcard, NewInstrs]), - execute_overlay(NewInstrs, Vars, BaseDir, TargetDir); -execute_overlay([{template, In, Out} | Rest], Vars, BaseDir, TargetDir) -> - InFile = rebar_templater:render(filename:join(BaseDir, In), Vars), - {ok, InFileData} = file:read_file(InFile), - OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars), - ok = filelib:ensure_dir(OutFile), - case file:write_file(OutFile, rebar_templater:render(InFileData, Vars)) of - ok -> - ok = apply_file_info(InFile, OutFile), - ?DEBUG("Templated ~p\n", [OutFile]), - execute_overlay(Rest, Vars, BaseDir, TargetDir); - {error, Reason} -> - ?ABORT("Failed to template ~p: ~p\n", [OutFile, Reason]) - end; -execute_overlay([{create, Out, Contents} | Rest], Vars, BaseDir, TargetDir) -> - OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars), - ok = filelib:ensure_dir(OutFile), - case file:write_file(OutFile, Contents) of - ok -> - ?DEBUG("Created ~p\n", [OutFile]), - execute_overlay(Rest, Vars, BaseDir, TargetDir); - {error, Reason} -> - ?ABORT("Failed to create ~p: ~p\n", [OutFile, Reason]) - end; -execute_overlay([{replace, Out, Regex, Replacement} | Rest], - Vars, BaseDir, TargetDir) -> - execute_overlay([{replace, Out, Regex, Replacement, []} | Rest], - Vars, BaseDir, TargetDir); -execute_overlay([{replace, Out, Regex, Replacement, Opts} | Rest], - Vars, BaseDir, TargetDir) -> - Filename = rebar_templater:render(filename:join(TargetDir, Out), Vars), - {ok, OrigData} = file:read_file(Filename), - Data = re:replace(OrigData, Regex, - rebar_templater:render(Replacement, Vars), - [global, {return, binary}] ++ Opts), - case file:write_file(Filename, Data) of - ok -> - ?DEBUG("Edited ~s: s/~s/~s/\n", [Filename, Regex, Replacement]), - execute_overlay(Rest, Vars, BaseDir, TargetDir); - {error, Reason} -> - ?ABORT("Failed to edit ~p: ~p\n", [Filename, Reason]) - end; -execute_overlay([Other | _Rest], _Vars, _BaseDir, _TargetDir) -> - {error, {unsupported_operation, Other}}. - - -apply_file_info(InFile, OutFile) -> - {ok, FileInfo} = file:read_file_info(InFile), - ok = file:write_file_info(OutFile, FileInfo). - -create_RELEASES(TargetDir, RelName, RelVsn) -> - ReleasesDir = filename:join(TargetDir, "releases"), - RelFile = filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]), - Apps = rebar_rel_utils:get_rel_apps(RelFile), - TargetLib = filename:join(TargetDir,"lib"), - - AppDirs = - [ {App, Vsn, TargetLib} - || {App, Vsn} <- Apps, - filelib:is_dir( - filename:join(TargetLib, - lists:concat([App, "-", Vsn]))) ], - - case release_handler:create_RELEASES( - code:root_dir(), - ReleasesDir, - RelFile, - AppDirs) of - ok -> - ok; - {error, Reason} -> - ?ABORT("Failed to create RELEASES file: ~p\n", - [Reason]) - end. diff --git a/src/rebar_shell.erl b/src/rebar_shell.erl index 0220a79..b089631 100644 --- a/src/rebar_shell.erl +++ b/src/rebar_shell.erl @@ -28,9 +28,37 @@ -module(rebar_shell). -author("Kresten Krab Thorup <krab@trifork.com>"). +-behaviour(rebar_provider). + +-export([init/1, + do/1]). + -include("rebar.hrl"). --export([shell/2, info/2]). +-define(PROVIDER, shell). +-define(DEPS, [app_builder]). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_config:config()) -> {ok, rebar_config:config()}. +init(State) -> + State1 = rebar_config:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + provides = shell, + bare = false, + deps = ?DEPS, + example = "rebar shell", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_config:config()) -> {ok, rebar_config:config()} | relx:error(). +do(Config) -> + shell(), + {ok, Config}. %% NOTE: %% this is an attempt to replicate `erl -pa ./ebin -pa deps/*/ebin`. it is @@ -39,7 +67,7 @@ %% it also lacks the ctrl-c interrupt handler that `erl` features. ctrl-c will %% immediately kill the script. ctrl-g, however, works fine -shell(_Config, _AppFile) -> +shell() -> true = code:add_pathz(rebar_utils:ebin_dir()), %% scan all processes for any with references to the old user and save them to %% update later @@ -88,4 +116,4 @@ wait_until_user_started(Timeout) -> %% if user is not yet registered wait a tenth of a second and try again undefined -> timer:sleep(100), wait_until_user_started(Timeout - 100); _ -> ok - end.
\ No newline at end of file + end. diff --git a/src/rebar_topo.erl b/src/rebar_topo.erl new file mode 100644 index 0000000..d6447c5 --- /dev/null +++ b/src/rebar_topo.erl @@ -0,0 +1,219 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you 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. +%%%------------------------------------------------------------------- +%%% @author Joe Armstrong +%%% @author Eric Merritt +%%% @doc +%%% This is a pretty simple topological sort for erlang. It was +%%% originally written for ermake by Joe Armstrong back in '98. It +%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relx. +%%% +%%% A partial order on the set S is a set of pairs {Xi,Xj} such that +%%% some relation between Xi and Xj is obeyed. +%%% +%%% A topological sort of a partial order is a sequence of elements +%%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial +%%% order i < j +%%% @end +%%%------------------------------------------------------------------- +-module(rebar_topo). + +-export([sort/1, + sort_apps/1, + format_error/1]). + +-include("rebar.hrl"). + +%%==================================================================== +%% Types +%%==================================================================== +-type pair() :: {DependentApp::atom(), PrimaryApp::atom()}. +-type name() :: AppName::atom(). +-type element() :: name() | pair(). + +%%==================================================================== +%% API +%%==================================================================== + +%% @doc This only does a topo sort on the list of applications and +%% assumes that there is only *one* version of each app in the list of +%% applications. This implies that you have already done the +%% constraint solve before you pass the list of apps here to be +%% sorted. +-spec sort_apps([rlx_app_info:t()]) -> + {ok, [rlx_app_info:t()]} | + relx:error(). +sort_apps(Apps) -> + Pairs = apps_to_pairs(Apps), + case sort(Pairs) of + {ok, Names} -> + {ok, names_to_apps(Names, Apps)}; + E -> + E + end. + +%% @doc Do a topological sort on the list of pairs. +-spec sort([pair()]) -> {ok, [atom()]} | relx:error(). +sort(Pairs) -> + iterate(Pairs, [], all(Pairs)). + +%% @doc nicely format the error from the sort. +-spec format_error(Reason::term()) -> iolist(). +format_error({cycle, Pairs}) -> + ["Cycle detected in dependency graph, this must be resolved " + "before we can continue:\n", + case Pairs of + [{P1, P2}] -> + [rlx_util:indent(2), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1)]; + [{P1, P2} | Rest] -> + [rlx_util:indent(2), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1), + [["-> ", erlang:atom_to_list(PP2), " -> ", erlang:atom_to_list(PP1)] || {PP1, PP2} <- Rest]]; + [] -> + [] + end]. + +%%==================================================================== +%% Internal Functions +%%==================================================================== +-spec names_to_apps([atom()], [rlx_app_info:t()]) -> [rlx_app_info:t()]. +names_to_apps(Names, Apps) -> + [find_app_by_name(Name, Apps) || Name <- Names]. + +-spec find_app_by_name(atom(), [rlx_app_info:t()]) -> rlx_app_info:t(). +find_app_by_name(Name, Apps) -> + {ok, App1} = + ec_lists:find(fun(App) -> + rlx_app_info:name(App) =:= Name + end, Apps), + App1. + +-spec apps_to_pairs([rlx_app_info:t()]) -> [pair()]. +apps_to_pairs(Apps) -> + lists:flatten([app_to_pairs(App) || App <- Apps]). + +-spec app_to_pairs(rlx_app_info:t()) -> [pair()]. +app_to_pairs(App) -> + [{DepApp, rlx_app_info:name(App)} || + DepApp <- + rlx_app_info:active_deps(App) ++ + rlx_app_info:library_deps(App)]. + + +%% @doc Iterate over the system. @private +-spec iterate([pair()], [name()], [name()]) -> + {ok, [name()]} | relx:error(). +iterate([], L, All) -> + {ok, remove_duplicates(L ++ subtract(All, L))}; +iterate(Pairs, L, All) -> + case subtract(lhs(Pairs), rhs(Pairs)) of + [] -> + ?ERROR(format_error({cycle, Pairs}), []); + Lhs -> + iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) + end. + +-spec all([pair()]) -> [atom()]. +all(L) -> + lhs(L) ++ rhs(L). + +-spec lhs([pair()]) -> [atom()]. +lhs(L) -> + [X || {X, _} <- L]. + +-spec rhs([pair()]) -> [atom()]. +rhs(L) -> + [Y || {_, Y} <- L]. + +%% @doc all the elements in L1 which are not in L2 +%% @private +-spec subtract([element()], [element()]) -> [element()]. +subtract(L1, L2) -> + [X || X <- L1, not lists:member(X, L2)]. + +%% @doc remove dups from the list. @private +-spec remove_duplicates([element()]) -> [element()]. +remove_duplicates([H|T]) -> + case lists:member(H, T) of + true -> + remove_duplicates(T); + false -> + [H|remove_duplicates(T)] + end; +remove_duplicates([]) -> + []. + +%% @doc +%% removes all pairs from L2 where the first element +%% of each pair is a member of L1 +%% +%% L2' L1 = [X] L2 = [{X,Y}]. +%% @private +-spec remove_pairs([atom()], [pair()]) -> [pair()]. +remove_pairs(L1, L2) -> + [All || All={X, _Y} <- L2, not lists:member(X, L1)]. + +%%==================================================================== +%% Tests +%%==================================================================== +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +topo_1_test() -> + Pairs = [{one,two},{two,four},{four,six}, + {two,ten},{four,eight}, + {six,three},{one,three}, + {three,five},{five,eight}, + {seven,five},{seven,nine}, + {nine,four},{nine,ten}], + ?assertMatch({ok, [one,seven,two,nine,four,six,three,five,eight,ten]}, + sort(Pairs)). +topo_2_test() -> + Pairs = [{app2, app1}, {zapp1, app1}, {stdlib, app1}, + {app3, app2}, {kernel, app1}, {kernel, app3}, + {app2, zapp1}, {app3, zapp1}, {zapp2, zapp1}], + ?assertMatch({ok, [stdlib, kernel, zapp2, + app3, app2, zapp1, app1]}, + sort(Pairs)). + +topo_pairs_cycle_test() -> + Pairs = [{app2, app1}, {app1, app2}, {stdlib, app1}], + ?assertMatch({error, {_, {cycle, [{app2, app1}, {app1, app2}]}}}, + sort(Pairs)). + +topo_apps_cycle_test() -> + {ok, App1} = rlx_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]), + {ok, App2} = rlx_app_info:new(app2, "0.1", "/no-dir", [app1], []), + Apps = [App1, App2], + ?assertMatch({error, {_, {cycle, [{app2,app1},{app1,app2}]}}}, + sort_apps(Apps)). + +topo_apps_good_test() -> + Apps = [App || + {ok, App} <- + [rlx_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]), + rlx_app_info:new(app2, "0.1", "/no-dir", [app3], []), + rlx_app_info:new(app3, "0.1", "/no-dir", [kernel], []), + rlx_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []), + rlx_app_info:new(stdlib, "0.1", "/no-dir", [], []), + rlx_app_info:new(kernel, "0.1", "/no-dir", [], []), + rlx_app_info:new(zapp2, "0.1", "/no-dir", [], [])]], + {ok, Sorted} = sort_apps(Apps), + ?assertMatch([stdlib, kernel, zapp2, + app3, app2, zapp1, app1], + [rlx_app_info:name(App) || App <- Sorted]). + +-endif. diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl deleted file mode 100644 index 3a38a08..0000000 --- a/src/rebar_upgrade.erl +++ /dev/null @@ -1,266 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2011 Joe Williams (joe@joetify.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_upgrade). - --include("rebar.hrl"). --include_lib("kernel/include/file.hrl"). - --export(['generate-upgrade'/2]). - -%% for internal use only --export([info/2]). - --define(TMP, "_tmp"). - -%% ==================================================================== -%% Public API -%% ==================================================================== - -'generate-upgrade'(Config0, ReltoolFile) -> - %% Get the old release path - {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile), - TargetParentDir = rebar_rel_utils:get_target_parent_dir(Config, - ReltoolConfig), - TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - - PrevRelPath = rebar_rel_utils:get_previous_release_path(Config), - OldVerPath = filename:join([TargetParentDir, PrevRelPath]), - - %% Run checks to make sure that building a package is possible - {NewVerPath, NewName, NewVer} = run_checks(Config, OldVerPath, - ReltoolConfig), - NameVer = NewName ++ "_" ++ NewVer, - - %% Save the code path prior to doing anything - OrigPath = code:get_path(), - - %% Prepare the environment for building the package - ok = setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer), - - %% Build the package - run_systools(NameVer, NewName), - - %% Boot file changes - {ok, _} = boot_files(TargetDir, NewVer, NewName), - - %% Extract upgrade and tar it back up with changes - make_tar(NameVer, NewVer, NewName), - - %% Clean up files that systools created - ok = cleanup(NameVer), - - %% Restore original path - true = code:set_path(OrigPath), - - {ok, Config}. - -%% =================================================================== -%% Internal functions -%% ================================================================== - -info(help, 'generate-upgrade') -> - ?CONSOLE("Build an upgrade package.~n" - "~n" - "Valid command line options:~n" - " previous_release=path~n" - " target_dir=target_dir (optional)~n", - []). - -run_checks(Config, OldVerPath, ReltoolConfig) -> - true = rebar_utils:prop_check(filelib:is_dir(OldVerPath), - "Release directory doesn't exist (~p)~n", - [OldVerPath]), - - {Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - - NewVerPath = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - true = rebar_utils:prop_check(filelib:is_dir(NewVerPath), - "Release directory doesn't exist (~p)~n", - [NewVerPath]), - - {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath), - {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), - - true = - rebar_utils:prop_check(NewName == OldName, - "New and old .rel release names do not match~n", - []), - true = - rebar_utils:prop_check(Name == NewName, - "Reltool and .rel release names do not match~n", - []), - true = - rebar_utils:prop_check(NewVer =/= OldVer, - "New and old .rel contain the same version~n", - []), - true = - rebar_utils:prop_check(Ver == NewVer, - "Reltool and .rel versions do not match~n", []), - - {NewVerPath, NewName, NewVer}. - -setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer) -> - Src = filename:join([NewVerPath, "releases", - NewVer, NewName ++ ".rel"]), - Dst = filename:join([".", NameVer ++ ".rel"]), - {ok, _} = file:copy(Src, Dst), - ok = code:add_pathsa( - lists:append([ - filelib:wildcard(filename:join([NewVerPath, - "lib", "*", "ebin"])), - filelib:wildcard(filename:join([OldVerPath, - "releases", "*"])), - filelib:wildcard(filename:join([OldVerPath, - "lib", "*", "ebin"])) - ])). - -run_systools(NewVer, Name) -> - Opts = [silent], - NameList = [Name], - case systools:make_relup(NewVer, NameList, NameList, Opts) of - {error, _, Msg} -> - ?ABORT("Systools [systools:make_relup/4] aborted with: ~p~n", - [Msg]); - _ -> - ?DEBUG("Relup created~n", []), - case systools:make_script(NewVer, Opts) of - {error, _, Msg1} -> - ?ABORT("Systools [systools:make_script/2] " - "aborted with: ~p~n", [Msg1]); - _ -> - ?DEBUG("Script created~n", []), - case systools:make_tar(NewVer, Opts) of - {error, _, Msg2} -> - ?ABORT("Systools [systools:make_tar/2] " - "aborted with: ~p~n", [Msg2]); - _ -> - ?DEBUG("Tarball created~n", []), - ok - end - end - end. - -boot_files(TargetDir, Ver, Name) -> - ok = file:make_dir(filename:join([".", ?TMP])), - ok = file:make_dir(filename:join([".", ?TMP, "releases"])), - ok = file:make_dir(filename:join([".", ?TMP, "releases", Ver])), - case os:type() of - {win32,_} -> - ok; - _ -> - ok = file:make_symlink( - filename:join(["start.boot"]), - filename:join([".", ?TMP, "releases", Ver, Name ++ ".boot"])) - end, - {ok, _} = - file:copy( - filename:join([TargetDir, "releases", Ver, "start_clean.boot"]), - filename:join([".", ?TMP, "releases", Ver, "start_clean.boot"])), - - SysConfig = filename:join([TargetDir, "releases", Ver, "sys.config"]), - _ = case filelib:is_regular(SysConfig) of - true -> - {ok, _} = file:copy( - SysConfig, - filename:join([".", ?TMP, "releases", Ver, - "sys.config"])); - false -> ok - end, - - VmArgs = filename:join([TargetDir, "releases", Ver, "vm.args"]), - case filelib:is_regular(VmArgs) of - true -> - {ok, _} = file:copy( - VmArgs, - filename:join([".", ?TMP, "releases", Ver, "vm.args"])); - false -> {ok, 0} - end. - -make_tar(NameVer, NewVer, NewName) -> - Filename = NameVer ++ ".tar.gz", - {ok, Cwd} = file:get_cwd(), - Absname = filename:join([Cwd, Filename]), - ok = file:set_cwd(?TMP), - ok = erl_tar:extract(Absname, [compressed]), - ok = file:delete(Absname), - case os:type() of - {win32,_} -> - {ok, _} = - file:copy( - filename:join([".", "releases", NewVer, "start.boot"]), - filename:join([".", "releases", NewVer, NewName ++ ".boot"])), - ok; - _ -> - ok - end, - {ok, Tar} = erl_tar:open(Absname, [write, compressed]), - ok = erl_tar:add(Tar, "lib", []), - ok = erl_tar:add(Tar, "releases", []), - ok = erl_tar:close(Tar), - ok = file:set_cwd(Cwd), - ?CONSOLE("~s upgrade package created~n", [NameVer]). - -cleanup(NameVer) -> - ?DEBUG("Removing files needed for building the upgrade~n", []), - Files = [ - filename:join([".", NameVer ++ ".rel"]), - filename:join([".", NameVer ++ ".boot"]), - filename:join([".", NameVer ++ ".script"]), - filename:join([".", "relup"]) - ], - lists:foreach(fun(F) -> ok = file:delete(F) end, Files), - - ok = remove_dir_tree(?TMP). - -%% adapted from http://www.erlang.org/doc/system_principles/create_target.html -remove_dir_tree(Dir) -> - remove_all_files(".", [Dir]). -remove_all_files(Dir, Files) -> - lists:foreach(fun(File) -> - FilePath = filename:join([Dir, File]), - {ok, FileInfo, Link} = file_info(FilePath), - case {Link, FileInfo#file_info.type} of - {false, directory} -> - {ok, DirFiles} = file:list_dir(FilePath), - remove_all_files(FilePath, DirFiles), - file:del_dir(FilePath); - _ -> - file:delete(FilePath) - end - end, Files). - -file_info(Path) -> - case file:read_file_info(Path) of - {ok, Info} -> - {ok, Info, false}; - {error, enoent} -> - {ok, Info} = file:read_link_info(Path), - {ok, Info, true}; - Error -> - Error - end. diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index f3a082d..8474a26 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -36,7 +36,7 @@ find_files/3, now_str/0, ensure_dir/1, - beam_to_mod/2, + beam_to_mod/1, beams/1, erl_to_mod/1, abort/0, @@ -503,9 +503,8 @@ sh_loop(Port, Fun, Acc) -> {error, {Rc, lists:flatten(lists:reverse(Acc))}} end. -beam_to_mod(Dir, Filename) -> - [Dir | Rest] = filename:split(Filename), - list_to_atom(filename:basename(string:join(Rest, "."), ".beam")). +beam_to_mod(Filename) -> + list_to_atom(filename:basename(Filename, ".beam")). erl_to_mod(Filename) -> list_to_atom(filename:rootname(filename:basename(Filename))). |