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.