You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2013/12/16 17:49:03 UTC
[06/10] Import rebar
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_asn1_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_asn1_compiler.erl b/src/rebar/src/rebar_asn1_compiler.erl
new file mode 100644
index 0000000..25e3fd3
--- /dev/null
+++ b/src/rebar/src/rebar_asn1_compiler.erl
@@ -0,0 +1,100 @@
+%% -*- 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"]))
+ ).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_base_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_base_compiler.erl b/src/rebar/src/rebar_base_compiler.erl
new file mode 100644
index 0000000..a0dec30
--- /dev/null
+++ b/src/rebar/src/rebar_base_compiler.erl
@@ -0,0 +1,260 @@
+%% -*- 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_base_compiler).
+
+-include("rebar.hrl").
+
+-export([run/4, run/7, run/8,
+ ok_tuple/3, error_tuple/5]).
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+run(Config, FirstFiles, RestFiles, CompileFn) ->
+ %% Compile the first files in sequence
+ compile_each(FirstFiles, Config, CompileFn),
+
+ %% Spin up workers for the rest of the files
+ case RestFiles of
+ [] ->
+ ok;
+ _ ->
+ Self = self(),
+ F = fun() -> compile_worker(Self, Config, CompileFn) end,
+ Jobs = rebar:get_jobs(Config),
+ ?DEBUG("Starting ~B compile worker(s)~n", [Jobs]),
+ Pids = [spawn_monitor(F) || _I <- lists:seq(1,Jobs)],
+ compile_queue(Pids, RestFiles)
+ end.
+
+run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
+ Compile3Fn) ->
+ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
+ Compile3Fn, [check_last_mod]).
+
+run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
+ Compile3Fn, Opts) ->
+ %% Convert simple extension to proper regex
+ SourceExtRe = ".*\\" ++ SourceExt ++ [$$],
+
+ Recursive = proplists:get_value(recursive, Opts, true),
+ %% Find all possible source files
+ FoundFiles = rebar_utils:find_files(SourceDir, SourceExtRe, Recursive),
+ %% Remove first files from found files
+ RestFiles = [Source || Source <- FoundFiles,
+ not lists:member(Source, FirstFiles)],
+
+ %% Check opts for flag indicating that compile should check lastmod
+ CheckLastMod = proplists:get_bool(check_last_mod, Opts),
+
+ run(Config, FirstFiles, RestFiles,
+ fun(S, C) ->
+ Target = target_file(S, SourceDir, SourceExt,
+ TargetDir, TargetExt),
+ simple_compile_wrapper(S, Target, Compile3Fn, C, CheckLastMod)
+ end).
+
+ok_tuple(Config, Source, Ws) ->
+ {ok, format_warnings(Config, Source, Ws)}.
+
+error_tuple(Config, Source, Es, Ws, Opts) ->
+ {error, format_errors(Config, Source, Es),
+ format_warnings(Config, Source, Ws, Opts)}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+simple_compile_wrapper(Source, Target, Compile3Fn, Config, false) ->
+ Compile3Fn(Source, Target, Config);
+simple_compile_wrapper(Source, Target, Compile3Fn, Config, true) ->
+ case filelib:last_modified(Target) < filelib:last_modified(Source) of
+ true ->
+ Compile3Fn(Source, Target, Config);
+ false ->
+ skipped
+ end.
+
+target_file(SourceFile, SourceDir, SourceExt, TargetDir, TargetExt) ->
+ %% Remove all leading components of the source dir from the file -- we want
+ %% to maintain the deeper structure (if any) of the source file path
+ BaseFile = remove_common_path(SourceFile, SourceDir),
+ filename:join([TargetDir, filename:dirname(BaseFile),
+ filename:basename(BaseFile, SourceExt) ++ TargetExt]).
+
+
+remove_common_path(Fname, Path) ->
+ remove_common_path1(filename:split(Fname), filename:split(Path)).
+
+remove_common_path1([Part | RestFilename], [Part | RestPath]) ->
+ remove_common_path1(RestFilename, RestPath);
+remove_common_path1(FilenameParts, _) ->
+ filename:join(FilenameParts).
+
+
+compile(Source, Config, CompileFn) ->
+ case CompileFn(Source, Config) of
+ ok ->
+ ok;
+ skipped ->
+ skipped;
+ Error ->
+ Error
+ end.
+
+compile_each([], _Config, _CompileFn) ->
+ ok;
+compile_each([Source | Rest], Config, CompileFn) ->
+ case compile(Source, Config, CompileFn) of
+ ok ->
+ ?CONSOLE("Compiled ~s\n", [Source]);
+ {ok, Warnings} ->
+ report(Warnings),
+ ?CONSOLE("Compiled ~s\n", [Source]);
+ skipped ->
+ ?INFO("Skipped ~s\n", [Source]);
+ Error ->
+ maybe_report(Error),
+ ?DEBUG("Compilation failed: ~p\n", [Error]),
+ ?FAIL
+ end,
+ compile_each(Rest, Config, CompileFn).
+
+compile_queue([], []) ->
+ ok;
+compile_queue(Pids, Targets) ->
+ receive
+ {next, Worker} ->
+ case Targets of
+ [] ->
+ Worker ! empty,
+ compile_queue(Pids, Targets);
+ [Source | Rest] ->
+ Worker ! {compile, Source},
+ compile_queue(Pids, Rest)
+ end;
+
+ {fail, Error} ->
+ maybe_report(Error),
+ ?DEBUG("Worker compilation failed: ~p\n", [Error]),
+ ?FAIL;
+
+ {compiled, Source, Warnings} ->
+ report(Warnings),
+ ?CONSOLE("Compiled ~s\n", [Source]),
+ compile_queue(Pids, Targets);
+
+ {compiled, Source} ->
+ ?CONSOLE("Compiled ~s\n", [Source]),
+ compile_queue(Pids, Targets);
+
+ {skipped, Source} ->
+ ?INFO("Skipped ~s\n", [Source]),
+ compile_queue(Pids, Targets);
+
+ {'DOWN', Mref, _, Pid, normal} ->
+ ?DEBUG("Worker exited cleanly\n", []),
+ Pids2 = lists:delete({Pid, Mref}, Pids),
+ compile_queue(Pids2, Targets);
+
+ {'DOWN', _Mref, _, _Pid, Info} ->
+ ?DEBUG("Worker failed: ~p\n", [Info]),
+ ?FAIL
+ end.
+
+compile_worker(QueuePid, Config, CompileFn) ->
+ QueuePid ! {next, self()},
+ receive
+ {compile, Source} ->
+ case catch(compile(Source, Config, CompileFn)) of
+ {ok, Ws} ->
+ QueuePid ! {compiled, Source, Ws},
+ compile_worker(QueuePid, Config, CompileFn);
+ ok ->
+ QueuePid ! {compiled, Source},
+ compile_worker(QueuePid, Config, CompileFn);
+ skipped ->
+ QueuePid ! {skipped, Source},
+ compile_worker(QueuePid, Config, CompileFn);
+ Error ->
+ QueuePid ! {fail, [{error, Error},
+ {source, Source}]},
+ ok
+ end;
+
+ empty ->
+ ok
+ end.
+
+format_errors(Config, Source, Errors) ->
+ format_errors(Config, Source, "", Errors).
+
+format_warnings(Config, Source, Warnings) ->
+ format_warnings(Config, Source, Warnings, []).
+
+format_warnings(Config, Source, Warnings, Opts) ->
+ Prefix = case lists:member(warnings_as_errors, Opts) of
+ true -> "";
+ false -> "Warning: "
+ end,
+ format_errors(Config, Source, Prefix, Warnings).
+
+maybe_report([{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}]) ->
+ maybe_report(ErrorsAndWarnings);
+maybe_report([{error, E}, {source, S}]) ->
+ report(["unexpected error compiling " ++ S, io_lib:fwrite("~n~p~n", [E])]);
+maybe_report({error, Es, Ws}) ->
+ report(Es),
+ report(Ws);
+maybe_report(_) ->
+ ok.
+
+report(Messages) ->
+ lists:foreach(fun(Msg) -> io:format("~s", [Msg]) end, Messages).
+
+format_errors(Config, _MainSource, Extra, Errors) ->
+ [begin
+ AbsSource = case rebar_utils:processing_base_dir(Config) of
+ true ->
+ Source;
+ false ->
+ filename:absname(Source)
+ end,
+ [format_error(AbsSource, Extra, Desc) || Desc <- Descs]
+ end
+ || {Source, Descs} <- Errors].
+
+format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) ->
+ ErrorDesc = Mod:format_error(Desc),
+ ?FMT("~s:~w:~w: ~s~s~n", [AbsSource, Line, Column, Extra, ErrorDesc]);
+format_error(AbsSource, Extra, {Line, Mod, Desc}) ->
+ ErrorDesc = Mod:format_error(Desc),
+ ?FMT("~s:~w: ~s~s~n", [AbsSource, Line, Extra, ErrorDesc]);
+format_error(AbsSource, Extra, {Mod, Desc}) ->
+ ErrorDesc = Mod:format_error(Desc),
+ ?FMT("~s: ~s~s~n", [AbsSource, Extra, ErrorDesc]).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_cleaner.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_cleaner.erl b/src/rebar/src/rebar_cleaner.erl
new file mode 100644
index 0000000..7a762f5
--- /dev/null
+++ b/src/rebar/src/rebar_cleaner.erl
@@ -0,0 +1,56 @@
+%% -*- 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_cleaner).
+
+-export([clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+clean(Config, _AppFile) ->
+ %% Get a list of files to delete from config and remove them
+ FilesToClean = rebar_config:get(Config, clean_files, []),
+ lists:foreach(fun (F) -> rebar_file_utils:rm_rf(F) end, FilesToClean).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, clean) ->
+ ?CONSOLE(
+ "Delete list of files.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {clean_files, ["file", "file2"]}
+ ]).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_config.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_config.erl b/src/rebar/src/rebar_config.erl
new file mode 100644
index 0000000..461de5d
--- /dev/null
+++ b/src/rebar/src/rebar_config.erl
@@ -0,0 +1,249 @@
+%% -*- 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_config).
+
+-export([new/0, new/1, base_config/1, consult_file/1,
+ get/3, get_local/3, get_list/3,
+ get_all/2,
+ set/3,
+ set_global/3, get_global/3,
+ is_verbose/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,
+ set_xconf/3, get_xconf/2, get_xconf/3, erase_xconf/2]).
+
+-include("rebar.hrl").
+
+-record(config, { dir :: file:filename(),
+ opts = [] :: list(),
+ globals = new_globals() :: dict(),
+ envs = new_env() :: dict(),
+ %% cross-directory/-command config
+ skip_dirs = new_skip_dirs() :: dict(),
+ xconf = new_xconf() :: dict() }).
+
+-export_type([config/0]).
+
+-opaque config() :: #config{}.
+
+-define(DEFAULT_NAME, "rebar.config").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+base_config(GlobalConfig) ->
+ ConfName = rebar_config:get_global(GlobalConfig, config, ?DEFAULT_NAME),
+ new(GlobalConfig, ConfName).
+
+new() ->
+ #config{dir = rebar_utils:get_cwd()}.
+
+new(ConfigFile) when is_list(ConfigFile) ->
+ case consult_file(ConfigFile) of
+ {ok, Opts} ->
+ #config { dir = rebar_utils:get_cwd(),
+ opts = Opts };
+ Other ->
+ ?ABORT("Failed to load ~s: ~p~n", [ConfigFile, Other])
+ end;
+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).
+
+get(Config, Key, Default) ->
+ proplists:get_value(Key, Config#config.opts, Default).
+
+get_list(Config, Key, Default) ->
+ get(Config, Key, Default).
+
+get_local(Config, Key, Default) ->
+ proplists:get_value(Key, local_opts(Config#config.opts, []), Default).
+
+get_all(Config, Key) ->
+ proplists:get_all_values(Key, Config#config.opts).
+
+set(Config, Key, Value) ->
+ Opts = proplists:delete(Key, Config#config.opts),
+ Config#config { opts = [{Key, Value} | Opts] }.
+
+set_global(Config, jobs=Key, Value) when is_list(Value) ->
+ set_global(Config, Key, list_to_integer(Value));
+set_global(Config, jobs=Key, Value) when is_integer(Value) ->
+ NewGlobals = dict:store(Key, erlang:max(1, Value), Config#config.globals),
+ Config#config{globals = NewGlobals};
+set_global(Config, Key, Value) ->
+ NewGlobals = dict:store(Key, Value, Config#config.globals),
+ Config#config{globals = NewGlobals}.
+
+get_global(Config, Key, Default) ->
+ case dict:find(Key, Config#config.globals) of
+ error ->
+ Default;
+ {ok, Value} ->
+ Value
+ end.
+
+is_verbose(Config) ->
+ DefaulLevel = rebar_log:default_level(),
+ get_global(Config, verbose, DefaulLevel) > DefaulLevel.
+
+consult_file(File) ->
+ case filename:extension(File) of
+ ".script" ->
+ consult_and_eval(remove_script_ext(File), File);
+ _ ->
+ Script = File ++ ".script",
+ case filelib:is_regular(Script) of
+ true ->
+ consult_and_eval(File, Script);
+ false ->
+ ?DEBUG("Consult config file ~p~n", [File]),
+ file:consult(File)
+ end
+ end.
+
+save_env(Config, Mod, Env) ->
+ NewEnvs = dict:store(Mod, Env, Config#config.envs),
+ Config#config{envs = NewEnvs}.
+
+get_env(Config, Mod) ->
+ dict:fetch(Mod, Config#config.envs).
+
+reset_envs(Config) ->
+ Config#config{envs = new_env()}.
+
+set_skip_dir(Config, Dir) ->
+ OldSkipDirs = Config#config.skip_dirs,
+ NewSkipDirs = case is_skip_dir(Config, Dir) of
+ false ->
+ ?DEBUG("Adding skip dir: ~s\n", [Dir]),
+ dict:store(Dir, true, OldSkipDirs);
+ true ->
+ OldSkipDirs
+ end,
+ Config#config{skip_dirs = NewSkipDirs}.
+
+is_skip_dir(Config, Dir) ->
+ dict:is_key(Dir, Config#config.skip_dirs).
+
+reset_skip_dirs(Config) ->
+ Config#config{skip_dirs = new_skip_dirs()}.
+
+set_xconf(Config, Key, Value) ->
+ NewXconf = dict:store(Key, Value, Config#config.xconf),
+ Config#config{xconf=NewXconf}.
+
+get_xconf(Config, Key) ->
+ {ok, Value} = dict:find(Key, Config#config.xconf),
+ Value.
+
+get_xconf(Config, Key, Default) ->
+ case dict:find(Key, Config#config.xconf) of
+ error ->
+ Default;
+ {ok, Value} ->
+ Value
+ end.
+
+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}.
+
+%% ===================================================================
+%% 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),
+ file:script(Script, bs([{'CONFIG', ConfigData}, {'SCRIPT', Script}])).
+
+remove_script_ext(F) ->
+ "tpircs." ++ Rev = lists:reverse(F),
+ lists:reverse(Rev).
+
+try_consult(File) ->
+ case file:consult(File) of
+ {ok, Terms} ->
+ ?DEBUG("Consult config file ~p~n", [File]),
+ Terms;
+ {error, enoent} ->
+ [];
+ {error, Reason} ->
+ ?ABORT("Failed to read config file ~s: ~p~n", [File, Reason])
+ end.
+
+bs(Vars) ->
+ lists:foldl(fun({K,V}, Bs) ->
+ 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().
+
+new_skip_dirs() -> dict:new().
+
+new_xconf() -> dict:new().
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_core.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_core.erl b/src/rebar/src/rebar_core.erl
new file mode 100644
index 0000000..631cef2
--- /dev/null
+++ b/src/rebar/src/rebar_core.erl
@@ -0,0 +1,608 @@
+%% -*- 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_core).
+
+-export([process_commands/2, help/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+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,
+
+ %% get plugin modules
+ Predirs = [],
+ 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,
+
+ lists:foreach(
+ fun(Cmd) ->
+ ?CONSOLE("==> help ~p~n~n", [Cmd]),
+ CmdModules = select_modules(AllModules, Cmd, []),
+ Modules = select_modules(CmdModules, info, []),
+ lists:foreach(fun(M) ->
+ ?CONSOLE("=== ~p:~p ===~n", [M, Cmd]),
+ M:info(help, Cmd),
+ ?CONSOLE("~n", [])
+ end, Modules)
+ 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(),
+ ParentConfig1, Command,
+ 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, ParentConfig, Command, DirSet) ->
+ case filelib:is_dir(Dir) of
+ false ->
+ ?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]),
+ {ParentConfig, DirSet};
+
+ true ->
+ 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(ModuleSet, Config, CurrentCodePath,
+ Dir, Command, DirSet)
+ end.
+
+skip_or_process_dir({[], undefined}=ModuleSet, Config, CurrentCodePath,
+ Dir, Command, DirSet) ->
+ process_dir1(Dir, Command, DirSet, Config, CurrentCodePath, ModuleSet);
+skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
+ Dir, Command, DirSet) ->
+ case lists:suffix(".app.src", ModuleSetFile)
+ orelse lists:suffix(".app", ModuleSetFile) of
+ true ->
+ %% .app or .app.src file, check if is_skipped_app
+ skip_or_process_dir1(ModuleSetFile, ModuleSet,
+ Config, CurrentCodePath, Dir,
+ Command, DirSet);
+ false ->
+ %% not an app dir, no need to consider apps=/skip_apps=
+ process_dir1(Dir, Command, DirSet, Config,
+ CurrentCodePath, ModuleSet)
+ end.
+
+skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
+ Dir, Command, DirSet) ->
+ 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, DirSet, Config1,
+ CurrentCodePath, ModuleSet);
+ {Config1, {true, SkippedApp}} ->
+ ?DEBUG("Skipping app: ~p~n", [SkippedApp]),
+ Config2 = increment_operations(Config1),
+ {Config2, DirSet};
+ {Config1, false} ->
+ process_dir1(Dir, Command, DirSet, Config1,
+ CurrentCodePath, ModuleSet)
+ end.
+
+process_dir1(Dir, Command, DirSet, Config, CurrentCodePath,
+ {DirModules, ModuleSetFile}) ->
+ 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,
+ ModuleSetFile),
+
+ %% 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),
+
+ {Config2, PluginPredirs} = acc_modules(PluginModules, preprocess,
+ Config1, ModuleSetFile),
+
+ AllPredirs = Predirs ++ PluginPredirs,
+
+ ?DEBUG("Predirs: ~p\n", [AllPredirs]),
+ {Config3, DirSet2} = process_each(AllPredirs, Command, Config2,
+ ModuleSetFile, DirSet),
+
+ %% Make sure the CWD is reset properly; processing the dirs may have
+ %% caused it to change
+ ok = file:set_cwd(Dir),
+
+ %% Check that this directory is not on the skip list
+ Config7 = case rebar_config:is_skip_dir(Config3, 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]),
+ Config3;
+
+ false ->
+ %% Check for and get command specific environments
+ {Config4, Env} = setup_envs(Config3, Modules),
+
+ %% Execute any before_command plugins on this directory
+ Config5 = execute_pre(Command, PluginModules,
+ Config4, ModuleSetFile, Env),
+
+ %% Execute the current command on this directory
+ Config6 = execute(Command, Modules ++ PluginModules,
+ Config5, ModuleSetFile, Env),
+
+ %% Execute any after_command plugins on this directory
+ execute_post(Command, PluginModules,
+ Config6, ModuleSetFile, Env)
+ end,
+
+ %% 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.
+ {Config8, Postdirs} = acc_modules(Modules ++ PluginModules, postprocess,
+ Config7, ModuleSetFile),
+ ?DEBUG("Postdirs: ~p\n", [Postdirs]),
+ Res = process_each(Postdirs, Command, Config8,
+ ModuleSetFile, DirSet3),
+
+ %% 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.
+
+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, _ModuleSetFile, DirSet) ->
+ %% reset cached (setup_env) envs
+ Config1 = rebar_config:reset_envs(Config),
+ {Config1, DirSet};
+process_each([Dir | Rest], Command, Config, ModuleSetFile, DirSet) ->
+ case sets:is_element(Dir, DirSet) of
+ true ->
+ ?DEBUG("Skipping ~s; already processed!\n", [Dir]),
+ process_each(Rest, Command, Config, ModuleSetFile, DirSet);
+ false ->
+ {Config1, DirSet2} = process_dir(Dir, Config, Command, DirSet),
+ Config2 = rebar_config:clean_config(Config, Config1),
+ %% reset cached (setup_env) envs
+ Config3 = rebar_config:reset_envs(Config2),
+ process_each(Rest, Command, Config3, ModuleSetFile, DirSet2)
+ 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, Modules, Config, ModuleFile, Env).
+
+%%
+%% Execute a command across all applicable modules
+%%
+execute(Command, Modules, Config, ModuleFile, Env) ->
+ case select_modules(Modules, Command, []) of
+ [] ->
+ Cmd = atom_to_list(Command),
+ case lists:prefix("pre_", Cmd)
+ orelse lists:prefix("post_", Cmd) of
+ true ->
+ ok;
+ false ->
+ ?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.
+
+expand_lib_dirs([], _Root, Acc) ->
+ Acc;
+expand_lib_dirs([Dir | Rest], Root, Acc) ->
+ Apps = filelib:wildcard(filename:join([Dir, "*", "ebin"])),
+ FqApps = case filename:pathtype(Dir) of
+ absolute -> Apps;
+ _ -> [filename:join([Root, A]) || A <- Apps]
+ 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, Env) ->
+ Hooks = rebar_config:get_local(Config, Mode, []),
+ 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)),
+ 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
+ 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),
+
+ %% Find relevant sources in base_dir and plugin_dir
+ Erls = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"),
+ RE = "^" ++ Erls ++ "\$",
+ %% 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) ->
+ 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]);
+ false ->
+ ?INFO("Loading plugin ~p from ~s~n", [Mod, Src]),
+ {module, Mod} = code:load_binary(Mod, Src, Bin),
+ Mod
+ end.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_ct.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_ct.erl b/src/rebar/src/rebar_ct.erl
new file mode 100644
index 0000000..74ae618
--- /dev/null
+++ b/src/rebar/src/rebar_ct.erl
@@ -0,0 +1,350 @@
+%% -*- 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.
+%% -------------------------------------------------------------------
+%%
+%% Targets:
+%% test - run common test suites in ./test
+%% int_test - run suites in ./int_test
+%% perf_test - run suites inm ./perf_test
+%%
+%% Global options:
+%% verbose=1 - show output from the common_test run as it goes
+%% suites="foo,bar" - run <test>/foo_SUITE and <test>/bar_SUITE
+%% case="mycase" - run individual test case foo_SUITE:mycase
+%% -------------------------------------------------------------------
+-module(rebar_ct).
+
+-export([ct/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+ct(Config, File) ->
+ TestDir = rebar_config:get_local(Config, ct_dir, "test"),
+ LogDir = rebar_config:get_local(Config, ct_log_dir, "logs"),
+ run_test_if_present(TestDir, LogDir, Config, File).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, ct) ->
+ ?CONSOLE(
+ "Run common_test suites.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ "Valid command line options:~n"
+ " suites=foo,bar - run <test>/foo_SUITE and <test>/bar_SUITE~n"
+ " case=\"mycase\" - run individual test case foo_SUITE:mycase~n",
+ [
+ {ct_dir, "itest"},
+ {ct_log_dir, "test/logs"},
+ {ct_extra_params, "-boot start_sasl -s myapp"},
+ {ct_use_short_names, true}
+ ]).
+
+run_test_if_present(TestDir, LogDir, Config, File) ->
+ case filelib:is_dir(TestDir) of
+ false ->
+ ?WARN("~s directory not present - skipping\n", [TestDir]),
+ ok;
+ true ->
+ case filelib:wildcard(TestDir ++ "/*_SUITE.{beam,erl}") of
+ [] ->
+ ?WARN("~s directory present, but no common_test"
+ ++ " SUITES - skipping\n", [TestDir]),
+ ok;
+ _ ->
+ try
+ run_test(TestDir, LogDir, Config, File)
+ catch
+ throw:skip ->
+ ok
+ end
+ end
+ end.
+
+run_test(TestDir, LogDir, Config, _File) ->
+ {Cmd, RawLog} = make_cmd(TestDir, LogDir, Config),
+ ?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
+ clear_log(LogDir, RawLog),
+ Output = case rebar_config:is_verbose(Config) of
+ false ->
+ " >> " ++ RawLog ++ " 2>&1";
+ true ->
+ " 2>&1 | tee -a " ++ RawLog
+ end,
+
+ ShOpts = [{env,[{"TESTDIR", TestDir}]}, return_on_error],
+ case rebar_utils:sh(Cmd ++ Output, ShOpts) of
+ {ok,_} ->
+ %% in older versions of ct_run, this could have been a failure
+ %% that returned a non-0 code. Check for that!
+ check_success_log(Config, RawLog);
+ {error,Res} ->
+ %% In newer ct_run versions, this may be a sign of a good compile
+ %% that failed cases. In older version, it's a worse error.
+ check_fail_log(Config, RawLog, Cmd ++ Output, Res)
+ end.
+
+clear_log(LogDir, RawLog) ->
+ case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
+ ok ->
+ NowStr = rebar_utils:now_str(),
+ LogHeader = "--- Test run on " ++ NowStr ++ " ---\n",
+ ok = file:write_file(RawLog, LogHeader);
+ {error, Reason} ->
+ ?ERROR("Could not create log dir - ~p\n", [Reason]),
+ ?FAIL
+ end.
+
+%% calling ct with erl does not return non-zero on failure - have to check
+%% log results
+check_success_log(Config, RawLog) ->
+ check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
+
+-type err_handler() :: fun((string()) -> no_return()).
+-spec failure_logger(string(), {integer(), string()}) -> err_handler().
+failure_logger(Command, {Rc, Output}) ->
+ fun(_Msg) ->
+ ?ABORT("~s failed with error: ~w and output:~n~s~n",
+ [Command, Rc, Output])
+ end.
+
+check_fail_log(Config, RawLog, Command, Result) ->
+ check_log(Config, RawLog, failure_logger(Command, Result)).
+
+check_log(Config,RawLog,Fun) ->
+ {ok, Msg} =
+ rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" "
+ ++ RawLog, [{use_stdout, false}]),
+ MakeFailed = string:str(Msg, "{error,make_failed}") =/= 0,
+ RunFailed = string:str(Msg, ", 0 failed") =:= 0,
+ if
+ MakeFailed ->
+ show_log(Config, RawLog),
+ ?ERROR("Building tests failed\n",[]),
+ ?FAIL;
+
+ RunFailed ->
+ show_log(Config, RawLog),
+ ?ERROR("One or more tests failed\n",[]),
+ ?FAIL;
+
+ true ->
+ Fun(Msg)
+ end.
+
+
+%% Show the log if it hasn't already been shown because verbose was on
+show_log(Config, RawLog) ->
+ ?CONSOLE("Showing log\n", []),
+ case rebar_config:is_verbose(Config) of
+ false ->
+ {ok, Contents} = file:read_file(RawLog),
+ ?CONSOLE("~s", [Contents]);
+ true ->
+ ok
+ end.
+
+make_cmd(TestDir, RawLogDir, Config) ->
+ Cwd = rebar_utils:get_cwd(),
+ LogDir = filename:join(Cwd, RawLogDir),
+ EbinDir = filename:absname(filename:join(Cwd, "ebin")),
+ IncludeDir = filename:join(Cwd, "include"),
+ Include = case filelib:is_dir(IncludeDir) of
+ true ->
+ " -include \"" ++ IncludeDir ++ "\"";
+ false ->
+ ""
+ end,
+
+ %% Check for the availability of ct_run; if we can't find it, generate a
+ %% warning and use the old school, less reliable approach to running CT.
+ BaseCmd = case os:find_executable("ct_run") of
+ false ->
+ "erl -noshell -s ct_run script_start -s erlang halt";
+ _ ->
+ "ct_run -noshell"
+ end,
+
+ %% Add the code path of the rebar process to the code path. This
+ %% includes the dependencies in the code path. The directories
+ %% that are part of the root Erlang install are filtered out to
+ %% avoid duplication
+ R = code:root_dir(),
+ NonLibCodeDirs = [P || P <- code:get_path(), not lists:prefix(R, P)],
+ CodeDirs = [io_lib:format("\"~s\"", [Dir]) ||
+ Dir <- [EbinDir|NonLibCodeDirs]],
+ CodePathString = string:join(CodeDirs, " "),
+ Cmd = case get_ct_specs(Cwd) of
+ undefined ->
+ ?FMT("~s"
+ " -pa ~s"
+ " ~s"
+ " ~s"
+ " -logdir \"~s\""
+ " -env TEST_DIR \"~s\""
+ " ~s",
+ [BaseCmd,
+ CodePathString,
+ Include,
+ build_name(Config),
+ LogDir,
+ filename:join(Cwd, TestDir),
+ get_extra_params(Config)]) ++
+ get_cover_config(Config, Cwd) ++
+ get_ct_config_file(TestDir) ++
+ get_config_file(TestDir) ++
+ get_suites(Config, TestDir) ++
+ get_case(Config);
+ SpecFlags ->
+ ?FMT("~s"
+ " -pa ~s"
+ " ~s"
+ " ~s"
+ " -logdir \"~s\""
+ " -env TEST_DIR \"~s\""
+ " ~s",
+ [BaseCmd,
+ CodePathString,
+ Include,
+ build_name(Config),
+ LogDir,
+ filename:join(Cwd, TestDir),
+ get_extra_params(Config)]) ++
+ SpecFlags ++ get_cover_config(Config, Cwd)
+ end,
+ RawLog = filename:join(LogDir, "raw.log"),
+ {Cmd, RawLog}.
+
+build_name(Config) ->
+ case rebar_config:get_local(Config, ct_use_short_names, false) of
+ true -> "-sname test";
+ false -> " -name test@" ++ net_adm:localhost()
+ end.
+
+get_extra_params(Config) ->
+ rebar_config:get_local(Config, ct_extra_params, "").
+
+get_ct_specs(Cwd) ->
+ case collect_glob(Cwd, ".*\.test\.spec\$") of
+ [] -> undefined;
+ [Spec] ->
+ " -spec " ++ Spec;
+ Specs ->
+ " -spec " ++
+ lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs])
+ end.
+
+get_cover_config(Config, Cwd) ->
+ case rebar_config:get_local(Config, cover_enabled, false) of
+ false ->
+ "";
+ true ->
+ case collect_glob(Cwd, ".*cover\.spec\$") of
+ [] ->
+ ?DEBUG("No cover spec found: ~s~n", [Cwd]),
+ "";
+ [Spec] ->
+ ?DEBUG("Found cover file ~w~n", [Spec]),
+ " -cover " ++ Spec;
+ Specs ->
+ ?ABORT("Multiple cover specs found: ~p~n", [Specs])
+ end
+ end.
+
+collect_glob(Cwd, Glob) ->
+ filelib:fold_files(Cwd, Glob, true, fun collect_files/2, []).
+
+collect_files(F, Acc) ->
+ %% Ignore any specs under the deps/ directory. Do this pulling
+ %% the dirname off the the F and then splitting it into a list.
+ Parts = filename:split(filename:dirname(F)),
+ case lists:member("deps", Parts) of
+ true ->
+ Acc; % There is a directory named "deps" in path
+ false ->
+ [F | Acc] % No "deps" directory in path
+ end.
+
+get_ct_config_file(TestDir) ->
+ Config = filename:join(TestDir, "test.config"),
+ case filelib:is_regular(Config) of
+ false ->
+ " ";
+ true ->
+ " -ct_config " ++ Config
+ end.
+
+get_config_file(TestDir) ->
+ Config = filename:join(TestDir, "app.config"),
+ case filelib:is_regular(Config) of
+ false ->
+ " ";
+ true ->
+ " -config " ++ Config
+ end.
+
+get_suites(Config, TestDir) ->
+ case rebar_config:get_global(Config, suites, undefined) of
+ undefined ->
+ " -dir " ++ TestDir;
+ Suites ->
+ Suites1 = string:tokens(Suites, ","),
+ Suites2 = [find_suite_path(Suite, TestDir) || Suite <- Suites1],
+ string:join([" -suite"] ++ Suites2, " ")
+ end.
+
+find_suite_path(Suite, TestDir) ->
+ Path = filename:join(TestDir, Suite ++ "_SUITE.erl"),
+ case filelib:is_regular(Path) of
+ false ->
+ ?WARN("Suite ~s not found\n", [Suite]),
+ %% Note - this throw is caught in run_test_if_present/3;
+ %% this solution was easier than refactoring the entire module.
+ throw(skip);
+ true ->
+ Path
+ end.
+
+get_case(Config) ->
+ case rebar_config:get_global(Config, 'case', undefined) of
+ undefined ->
+ "";
+ Case ->
+ " -case " ++ Case
+ end.