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:00 UTC

[03/10] Import rebar

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_protobuffs_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_protobuffs_compiler.erl b/src/rebar/src/rebar_protobuffs_compiler.erl
new file mode 100644
index 0000000..579ecfb
--- /dev/null
+++ b/src/rebar/src/rebar_protobuffs_compiler.erl
@@ -0,0 +1,153 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_protobuffs_compiler).
+
+-export([compile/2,
+         clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+compile(Config, _AppFile) ->
+    case rebar_utils:find_files("src", ".*\\.proto$") of
+        [] ->
+            ok;
+        FoundFiles ->
+            %% Check for protobuffs library -- if it's not present, fail
+            %% since we have.proto files that need building
+            case protobuffs_is_present() of
+                true ->
+                    %% Build a list of output files - { Proto, Beam, Hrl }
+                    Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} ||
+                                  Proto <- FoundFiles],
+
+                    %% Compile each proto file
+                    compile_each(Config, Targets);
+                false ->
+                    ?ERROR("Protobuffs library not present in code path!\n",
+                           []),
+                    ?FAIL
+            end
+    end.
+
+clean(_Config, _AppFile) ->
+    %% Get a list of generated .beam and .hrl files and then delete them
+    Protos = rebar_utils:find_files("src", ".*\\.proto$"),
+    BeamFiles = [fq_beam_file(F) || F <- Protos],
+    HrlFiles = [fq_hrl_file(F) || F <- Protos],
+    Targets = BeamFiles ++ HrlFiles,
+    case Targets of
+        [] ->
+            ok;
+        _ ->
+            delete_each(Targets)
+    end.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+    info_help("Build Protobuffs (*.proto) sources");
+info(help, clean) ->
+    info_help("Delete Protobuffs (*.proto) build results").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  erl_opts is passed as compile_flags to "
+       "protobuffs_compile:scan_file/2~n",
+       [Description]).
+
+protobuffs_is_present() ->
+    code:which(protobuffs_compile) =/= non_existing.
+
+beam_file(Proto) ->
+    filename:basename(Proto, ".proto") ++ "_pb.beam".
+
+hrl_file(Proto) ->
+    filename:basename(Proto, ".proto") ++ "_pb.hrl".
+
+fq_beam_file(Proto) ->
+    filename:join(["ebin", filename:basename(Proto, ".proto") ++ "_pb.beam"]).
+
+fq_hrl_file(Proto) ->
+    filename:join(["include", filename:basename(Proto, ".proto") ++ "_pb.hrl"]).
+
+needs_compile(Proto, Beam) ->
+    ActualBeam = filename:join(["ebin", filename:basename(Beam)]),
+    filelib:last_modified(ActualBeam) < filelib:last_modified(Proto).
+
+compile_each(_, []) ->
+    ok;
+compile_each(Config, [{Proto, Beam, Hrl} | Rest]) ->
+    case needs_compile(Proto, Beam) of
+        true ->
+            ?CONSOLE("Compiling ~s\n", [Proto]),
+            ErlOpts = rebar_utils:erl_opts(Config),
+            case protobuffs_compile:scan_file(Proto,
+                                              [{compile_flags,ErlOpts}]) of
+                ok ->
+                    %% Compilation worked, but we need to move the
+                    %% beam and .hrl file into the ebin/ and include/
+                    %% directories respectively
+                    %% TODO: Protobuffs really needs to be better about this
+                    ok = filelib:ensure_dir(filename:join("ebin","dummy")),
+                    ok = rebar_file_utils:mv(Beam, "ebin"),
+                    ok = filelib:ensure_dir(filename:join("include", Hrl)),
+                    ok = rebar_file_utils:mv(Hrl, "include"),
+                    ok;
+                Other ->
+                    ?ERROR("Protobuffs compile of ~s failed: ~p\n",
+                           [Proto, Other]),
+                    ?FAIL
+            end;
+        false ->
+            ok
+    end,
+    compile_each(Config, Rest).
+
+delete_each([]) ->
+    ok;
+delete_each([File | Rest]) ->
+    case file:delete(File) of
+        ok ->
+            ok;
+        {error, enoent} ->
+            ok;
+        {error, Reason} ->
+            ?ERROR("Failed to delete ~s: ~p\n", [File, Reason])
+    end,
+    delete_each(Rest).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_qc.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_qc.erl b/src/rebar/src/rebar_qc.erl
new file mode 100644
index 0000000..53a6f52
--- /dev/null
+++ b/src/rebar/src/rebar_qc.erl
@@ -0,0 +1,187 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2011-2012 Tuncer Ayaz
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_qc).
+
+-export([qc/2, triq/2, eqc/2, clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+-define(QC_DIR, ".qc").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+qc(Config, _AppFile) ->
+    ?CONSOLE("NOTICE: Using experimental 'qc' command~n", []),
+    run_qc(Config, qc_opts(Config)).
+
+triq(Config, _AppFile) ->
+    ?CONSOLE("NOTICE: Using experimental 'triq' command~n", []),
+    ok = load_qc_mod(triq),
+    run_qc(Config, qc_opts(Config), triq).
+
+eqc(Config, _AppFile) ->
+    ?CONSOLE("NOTICE: Using experimental 'eqc' command~n", []),
+    ok = load_qc_mod(eqc),
+    run_qc(Config, qc_opts(Config), eqc).
+
+clean(_Config, _File) ->
+    rebar_file_utils:rm_rf(?QC_DIR).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, qc) ->
+    ?CONSOLE(
+       "Test QuickCheck properties.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  {qc_opts, [{qc_mod, module()}, Options]}~n"
+       "  ~p~n"
+       "  ~p~n",
+       [
+        {qc_compile_opts, []},
+        {qc_first_files, []}
+       ]).
+
+-define(TRIQ_MOD, triq).
+-define(EQC_MOD, eqc).
+
+qc_opts(Config) ->
+    rebar_config:get(Config, qc_opts, []).
+
+run_qc(Config, QCOpts) ->
+    run_qc(Config, QCOpts, select_qc_mod(QCOpts)).
+
+run_qc(Config, RawQCOpts, QC) ->
+    ?DEBUG("Selected QC module: ~p~n", [QC]),
+    QCOpts = lists:filter(fun({qc_mod, _}) -> false;
+                             (_) -> true
+                          end, RawQCOpts),
+    run(Config, QC, QCOpts).
+
+select_qc_mod(QCOpts) ->
+    case proplists:get_value(qc_mod, QCOpts) of
+        undefined ->
+            detect_qc_mod();
+        QC ->
+            case code:ensure_loaded(QC) of
+                {module, QC} ->
+                    QC;
+                {error, nofile} ->
+                    ?ABORT("Configured QC library '~p' not available~n", [QC])
+            end
+    end.
+
+detect_qc_mod() ->
+    case code:ensure_loaded(?TRIQ_MOD) of
+        {module, ?TRIQ_MOD} ->
+            ?TRIQ_MOD;
+        {error, nofile} ->
+            case code:ensure_loaded(?EQC_MOD) of
+                {module, ?EQC_MOD} ->
+                    ?EQC_MOD;
+                {error, nofile} ->
+                    ?ABORT("No QC library available~n", [])
+            end
+    end.
+
+load_qc_mod(Mod) ->
+    case code:ensure_loaded(Mod) of
+        {module, Mod} ->
+            ok;
+        {error, nofile} ->
+            ?ABORT("Failed to load QC lib '~p'~n", [Mod])
+    end.
+
+ensure_dirs() ->
+    ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")),
+    ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
+
+setup_codepath() ->
+    CodePath = code:get_path(),
+    true = code:add_patha(qc_dir()),
+    true = code:add_pathz(rebar_utils:ebin_dir()),
+    CodePath.
+
+qc_dir() ->
+    filename:join(rebar_utils:get_cwd(), ?QC_DIR).
+
+run(Config, QC, QCOpts) ->
+    ?DEBUG("qc_opts: ~p~n", [QCOpts]),
+
+    ok = ensure_dirs(),
+    CodePath = setup_codepath(),
+
+    CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
+                                                      false),
+    %% Compile erlang code to ?QC_DIR, using a tweaked config
+    %% with appropriate defines, and include all the test modules
+    %% as well.
+    {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR),
+
+    case CompileOnly of
+        "true" ->
+            true = code:set_path(CodePath),
+            ?CONSOLE("Compiled modules for qc~n", []);
+        false ->
+            run1(QC, QCOpts, CodePath)
+    end.
+
+run1(QC, QCOpts, CodePath) ->
+    TestModule = fun(M) -> qc_module(QC, QCOpts, M) end,
+    case lists:flatmap(TestModule, find_prop_mods()) of
+        [] ->
+            true = code:set_path(CodePath),
+            ok;
+        Errors ->
+            ?ABORT("One or more QC properties didn't hold true:~n~p~n",
+                   [Errors])
+    end.
+
+qc_module(QC=triq, _QCOpts, M) ->
+    case QC:module(M) of
+        true ->
+            [];
+        Failed ->
+            [Failed]
+    end;
+qc_module(QC=eqc, [], M) -> QC:module(M);
+qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
+
+find_prop_mods() ->
+    Beams = rebar_utils:find_files(?QC_DIR, ".*\\.beam\$"),
+    [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)].
+
+has_prop(Mod) ->
+    lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end,
+              Mod:module_info(exports)).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_rel_utils.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_rel_utils.erl b/src/rebar/src/rebar_rel_utils.erl
new file mode 100644
index 0000000..085dbd9
--- /dev/null
+++ b/src/rebar/src/rebar_rel_utils.erl
@@ -0,0 +1,238 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_rel_utils).
+
+-export([is_rel_dir/0,
+         is_rel_dir/1,
+         get_reltool_release_info/1,
+         get_rel_release_info/1,
+         get_rel_release_info/2,
+         get_rel_apps/1,
+         get_rel_apps/2,
+         get_previous_release_path/1,
+         get_rel_file_path/2,
+         load_config/2,
+         get_sys_tuple/1,
+         get_target_dir/2,
+         get_root_dir/2,
+         get_target_parent_dir/2]).
+
+-include("rebar.hrl").
+
+is_rel_dir() ->
+    is_rel_dir(rebar_utils:get_cwd()).
+
+is_rel_dir(Dir) ->
+    Fname = filename:join([Dir, "reltool.config"]),
+    Scriptname = Fname ++ ".script",
+    Res = case filelib:is_regular(Scriptname) of
+              true ->
+                  {true, Scriptname};
+              false ->
+                  case filelib:is_regular(Fname) of
+                      true ->
+                          {true, Fname};
+                      false ->
+                          false
+                  end
+          end,
+    ?DEBUG("is_rel_dir(~s) -> ~p~n", [Dir, Res]),
+    Res.
+
+%% Get release name and version from a reltool.config
+get_reltool_release_info([{sys, Config}| _]) ->
+    {rel, Name, Ver, _} = proplists:lookup(rel, Config),
+    {Name, Ver};
+get_reltool_release_info(ReltoolFile) when is_list(ReltoolFile) ->
+    case file:consult(ReltoolFile) of
+        {ok, ReltoolConfig} ->
+            get_reltool_release_info(ReltoolConfig);
+        _ ->
+            ?ABORT("Failed to parse ~s~n", [ReltoolFile])
+    end.
+
+%% Get release name and version from a rel file
+get_rel_release_info(RelFile) ->
+    case file:consult(RelFile) of
+        {ok, [{release, {Name, Ver}, _, _}]} ->
+            {Name, Ver};
+        _ ->
+            ?ABORT("Failed to parse ~s~n", [RelFile])
+    end.
+
+%% Get release name and version from a name and a path
+get_rel_release_info(Name, Path) ->
+    RelPath = get_rel_file_path(Name, Path),
+    get_rel_release_info(RelPath).
+
+%% Get list of apps included in a release from a rel file
+get_rel_apps(RelFile) ->
+    case file:consult(RelFile) of
+        {ok, [{release, _, _, Apps}]} ->
+            make_proplist(Apps, []);
+        _ ->
+            ?ABORT("Failed to parse ~s~n", [RelFile])
+    end.
+
+%% Get list of apps included in a release from a name and a path
+get_rel_apps(Name, Path) ->
+    RelPath = get_rel_file_path(Name, Path),
+    get_rel_apps(RelPath).
+
+%% Get rel file path from name and path
+get_rel_file_path(Name, Path) ->
+    [RelFile] = filelib:wildcard(filename:join([Path, "releases", "*",
+                                                Name ++ ".rel"])),
+    RelFile.
+
+%% Get the previous release path from a global variable
+get_previous_release_path(Config) ->
+    case rebar_config:get_global(Config, previous_release, false) of
+        false ->
+            ?ABORT("previous_release=PATH is required to "
+                   "create upgrade package~n", []);
+        OldVerPath ->
+            OldVerPath
+    end.
+
+%%
+%% Load terms from reltool.config
+%%
+load_config(Config, ReltoolFile) ->
+    case rebar_config:consult_file(ReltoolFile) of
+        {ok, Terms} ->
+            expand_version(Config, Terms, filename:dirname(ReltoolFile));
+        Other ->
+            ?ABORT("Failed to load expected config from ~s: ~p\n",
+                   [ReltoolFile, Other])
+    end.
+
+%%
+%% Look for the {sys, [...]} tuple in the reltool.config file.
+%% Without this present, we can't run reltool.
+%%
+get_sys_tuple(ReltoolConfig) ->
+    case lists:keyfind(sys, 1, ReltoolConfig) of
+        {sys, _} = SysTuple ->
+            SysTuple;
+        false ->
+            ?ABORT("Failed to find {sys, [...]} tuple in reltool.config.", [])
+    end.
+
+%%
+%% Look for {target_dir, TargetDir} in the reltool config file; if none is
+%% found, use the name of the release as the default target directory.
+%%
+get_target_dir(Config, ReltoolConfig) ->
+    case rebar_config:get_global(Config, target_dir, undefined) of
+        undefined ->
+            case lists:keyfind(target_dir, 1, ReltoolConfig) of
+                {target_dir, TargetDir} ->
+                    filename:absname(TargetDir);
+                false ->
+                    {sys, SysInfo} = get_sys_tuple(ReltoolConfig),
+                    case lists:keyfind(rel, 1, SysInfo) of
+                        {rel, Name, _Vsn, _Apps} ->
+                            filename:absname(Name);
+                        false ->
+                            filename:absname("target")
+                    end
+            end;
+        TargetDir ->
+            filename:absname(TargetDir)
+    end.
+
+get_target_parent_dir(Config, ReltoolConfig) ->
+    TargetDir = get_target_dir(Config, ReltoolConfig),
+    case lists:reverse(tl(lists:reverse(filename:split(TargetDir)))) of
+        [] -> ".";
+        Components -> filename:join(Components)
+    end.
+
+%%
+%% Look for root_dir in sys tuple and command line; fall back to
+%% code:root_dir().
+%%
+get_root_dir(Config, ReltoolConfig) ->
+    {sys, SysInfo} = get_sys_tuple(ReltoolConfig),
+    SysRootDirTuple = lists:keyfind(root_dir, 1, SysInfo),
+    CmdRootDir = rebar_config:get_global(Config, root_dir, undefined),
+    case {SysRootDirTuple, CmdRootDir} of
+        %% root_dir in sys typle and no root_dir on cmd-line
+        {{root_dir, SysRootDir}, undefined} ->
+            SysRootDir;
+        %% root_dir in sys typle and also root_dir on cmd-line
+        {{root_dir, SysRootDir}, CmdRootDir} when CmdRootDir =/= undefined ->
+            case string:equal(SysRootDir, CmdRootDir) of
+                true ->
+                    ok;
+                false ->
+                    ?WARN("overriding reltool.config root_dir with "
+                          "different command line root_dir~n", [])
+            end,
+            CmdRootDir;
+        %% no root_dir in sys typle and no root_dir on cmd-line
+        {false, undefined} ->
+            code:root_dir();
+        %% no root_dir in sys tuple but root_dir on cmd-line
+        {false, CmdRootDir} when CmdRootDir =/= undefined ->
+            CmdRootDir
+    end.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+make_proplist([{_,_}=H|T], Acc) ->
+    make_proplist(T, [H|Acc]);
+make_proplist([H|T], Acc) ->
+    App = element(1, H),
+    Ver = element(2, H),
+    make_proplist(T, [{App,Ver}|Acc]);
+make_proplist([], Acc) ->
+    Acc.
+
+expand_version(Config, ReltoolConfig, Dir) ->
+    case lists:keyfind(sys, 1, ReltoolConfig) of
+        {sys, Sys} ->
+            {Config1, Rels} =
+                lists:foldl(
+                  fun(Term, {C, R}) ->
+                          {C1, Rel} = expand_rel_version(C, Term, Dir),
+                          {C1, [Rel|R]}
+                  end, {Config, []}, Sys),
+            ExpandedSys = {sys, lists:reverse(Rels)},
+            {Config1, lists:keyreplace(sys, 1, ReltoolConfig, ExpandedSys)};
+        _ ->
+            {Config, ReltoolConfig}
+    end.
+
+expand_rel_version(Config, {rel, Name, Version, Apps}, Dir) ->
+    {NewConfig, VsnString} = rebar_utils:vcs_vsn(Config, Version, Dir),
+    {NewConfig, {rel, Name, VsnString, Apps}};
+expand_rel_version(Config, Other, _Dir) ->
+    {Config, Other}.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_reltool.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_reltool.erl b/src/rebar/src/rebar_reltool.erl
new file mode 100644
index 0000000..9f9488e
--- /dev/null
+++ b/src/rebar/src/rebar_reltool.erl
@@ -0,0 +1,408 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_reltool).
+
+-export([generate/2,
+         overlay/2,
+         clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+-include_lib("kernel/include/file.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+generate(Config0, ReltoolFile) ->
+    %% Make sure we have decent version of reltool available
+    check_vsn(),
+
+    %% Load the reltool configuration from the file
+    {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile),
+
+    Sys = rebar_rel_utils:get_sys_tuple(ReltoolConfig),
+
+    %% Spin up reltool server and load our config into it
+    {ok, Server} = reltool:start_server([Sys]),
+
+    %% Do some validation of the reltool configuration; error messages out of
+    %% reltool are still pretty cryptic
+    validate_rel_apps(Server, Sys),
+
+    %% Finally, run reltool
+    case catch(run_reltool(Server, Config, ReltoolConfig)) of
+        ok ->
+            {ok, Config};
+        {error, failed} ->
+            ?FAIL;
+        Other2 ->
+            ?ERROR("Unexpected error: ~p\n", [Other2]),
+            ?FAIL
+    end.
+
+overlay(Config, ReltoolFile) ->
+    %% Load the reltool configuration from the file
+    {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
+    {process_overlay(Config, ReltoolConfig), Config1}.
+
+clean(Config, ReltoolFile) ->
+    {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
+    TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
+    rebar_file_utils:rm_rf(TargetDir),
+    rebar_file_utils:delete_each(["reltool.spec"]),
+    {ok, Config1}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, generate) ->
+    info_help("Build release with reltool");
+info(help, clean) ->
+    info_help("Delete release");
+info(help, overlay) ->
+    info_help("Run reltool overlays only").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~n"
+       "Valid reltool.config options:~n"
+       "  {sys, []}~n"
+       "  {target_dir, \"target\"}~n"
+       "  {overlay_vars, \"overlay\"}~n"
+       "  {overlay, []}~n"
+       "Valid command line options:~n"
+       "  target_dir=target~n"
+       "  overlay_vars=VarsFile~n"
+       "  dump_spec=1 (write reltool target spec to reltool.spec)~n",
+       [
+        Description
+       ]).
+
+check_vsn() ->
+    %% TODO: use application:load and application:get_key once we require
+    %%       R14A or newer. There's no reltool.app before R14A.
+    case code:lib_dir(reltool) of
+        {error, bad_name} ->
+            ?ABORT("Reltool support requires the reltool application "
+                   "to be installed!", []);
+        Path ->
+            ReltoolVsn = filename:basename(Path),
+            case ReltoolVsn < "reltool-0.5.2" of
+                true ->
+                    ?ABORT("Reltool support requires at least reltool-0.5.2; "
+                           "this VM is using ~s\n", [ReltoolVsn]);
+                false ->
+                    ok
+            end
+    end.
+
+process_overlay(Config, ReltoolConfig) ->
+    TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
+
+    {_BootRelName, BootRelVsn} =
+        rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
+
+    %% Initialize overlay vars with some basics
+    %% (that can get overwritten)
+    OverlayVars0 =
+        dict:from_list([{erts_vsn, "erts-" ++ erlang:system_info(version)},
+                        {rel_vsn, BootRelVsn},
+                        {target_dir, TargetDir},
+                        {hostname, net_adm:localhost()}]),
+
+    %% Load up any variables specified by overlay_vars
+    OverlayVars1 = overlay_vars(Config, OverlayVars0, ReltoolConfig),
+    OverlayVars = rebar_templater:resolve_variables(dict:to_list(OverlayVars1),
+                                                    OverlayVars1),
+
+    %% Finally, overlay the files specified by the overlay section
+    case lists:keyfind(overlay, 1, ReltoolConfig) of
+        {overlay, Overlay} when is_list(Overlay) ->
+            execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(),
+                            TargetDir);
+        false ->
+            ?INFO("No {overlay, [...]} found in reltool.config.\n", []);
+        _ ->
+            ?ABORT("{overlay, [...]} entry in reltool.config "
+                   "must be a list.\n", [])
+    end.
+
+%%
+%% Look for overlay_vars file reference. If the user provides an overlay_vars on
+%% the command line (i.e. a global), the terms from that file OVERRIDE the one
+%% listed in reltool.config. To re-iterate, this means you can specify a
+%% variable in the file from reltool.config and then override that value by
+%% providing an additional file on the command-line.
+%%
+overlay_vars(Config, Vars0, ReltoolConfig) ->
+    BaseVars = load_vars_file([proplists:get_value(overlay_vars, ReltoolConfig)]),
+    OverlayVars = rebar_config:get_global(Config, overlay_vars, []),
+    OverrideVars = load_vars_file(string:tokens(OverlayVars, ",")),
+    M = fun merge_overlay_var/3, 
+    dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars).
+
+merge_overlay_var(_Key, _Base, Override) -> Override.
+
+%%
+%% If a filename is provided, construct a dict of terms
+%%
+load_vars_file([undefined]) ->
+    dict:new();
+load_vars_file([]) ->
+    dict:new();
+load_vars_file(Files) ->
+    load_vars_file(Files, dict:new()).
+
+load_vars_file([], Dict) ->
+    Dict;
+load_vars_file([File | Files], BaseVars) ->
+    case rebar_config:consult_file(File) of
+        {ok, Terms} ->
+            OverrideVars = dict:from_list(Terms),
+            M = fun merge_overlay_var/3,
+            load_vars_file(Files, dict:merge(M, BaseVars, OverrideVars));
+        {error, Reason} ->
+            ?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason])
+    end.
+
+validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) ->
+    case lists:keyfind(rel, 1, ReltoolConfig) of
+        false ->
+            ok;
+        {rel, _Name, _Vsn, Apps} ->
+            %% Identify all the apps that do NOT exist, based on
+            %% what's available from the reltool server
+            Missing = lists:sort(
+                        [App || App <- Apps,
+                                app_exists(App, ReltoolServer) == false]),
+            case Missing of
+                [] ->
+                    ok;
+                _ ->
+                    ?ABORT("Apps in {rel, ...} section not found by "
+                           "reltool: ~p\n", [Missing])
+            end;
+        Rel ->
+            %% Invalid release format!
+            ?ABORT("Invalid {rel, ...} section in reltools.config: ~p\n", [Rel])
+    end.
+
+app_exists(App, Server) when is_atom(App) ->
+    case reltool_server:get_app(Server, App) of
+        {ok, _} ->
+            true;
+        _ ->
+            false
+    end;
+app_exists(AppTuple, Server) when is_tuple(AppTuple) ->
+    app_exists(element(1, AppTuple), Server).
+
+run_reltool(Server, Config, ReltoolConfig) ->
+    case reltool:get_target_spec(Server) of
+        {ok, Spec} ->
+            %% Pull the target dir and make sure it exists
+            TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
+            mk_target_dir(Config, TargetDir),
+
+            %% Determine the otp root dir to use
+            RootDir = rebar_rel_utils:get_root_dir(Config, ReltoolConfig),
+
+            %% Dump the spec, if necessary
+            dump_spec(Config, Spec),
+
+            %% Have reltool actually run
+            case reltool:eval_target_spec(Spec, RootDir, TargetDir) of
+                ok ->
+                    ok;
+                {error, Reason} ->
+                    ?ABORT("Failed to generate target from spec: ~p\n",
+                           [Reason])
+            end,
+
+            {BootRelName, BootRelVsn} =
+                rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
+
+            ok = create_RELEASES(TargetDir, BootRelName, BootRelVsn),
+
+            process_overlay(Config, ReltoolConfig);
+
+        {error, Reason} ->
+            ?ABORT("Unable to generate spec: ~s\n", [Reason])
+    end.
+
+mk_target_dir(Config, TargetDir) ->
+    case filelib:ensure_dir(filename:join(TargetDir, "dummy")) of
+        ok ->
+            ok;
+        {error, eexist} ->
+            %% Output directory already exists; if force=1, wipe it out
+            case rebar_config:get_global(Config, force, "0") of
+                "1" ->
+                    rebar_file_utils:rm_rf(TargetDir),
+                    ok = file:make_dir(TargetDir);
+                _ ->
+                    ?ERROR("Release target directory ~p already exists!\n",
+                           [TargetDir]),
+                    ?FAIL
+            end;
+        {error, Reason} ->
+            ?ERROR("Failed to make target dir ~p: ~s\n",
+                   [TargetDir, file:format_error(Reason)]),
+            ?FAIL
+    end.
+
+dump_spec(Config, Spec) ->
+    case rebar_config:get_global(Config, dump_spec, "0") of
+        "1" ->
+            SpecBin = list_to_binary(io_lib:print(Spec, 1, 120, -1)),
+            ok = file:write_file("reltool.spec", SpecBin);
+        _ ->
+            ok
+    end.
+
+
+%% TODO: Merge functionality here with rebar_templater
+
+execute_overlay([], _Vars, _BaseDir, _TargetDir) ->
+    ok;
+execute_overlay([{mkdir, Out} | Rest], Vars, BaseDir, TargetDir) ->
+    OutFile = rebar_templater:render(
+                filename:join([TargetDir, Out, "dummy"]), Vars),
+    ok = filelib:ensure_dir(OutFile),
+    ?DEBUG("Created dir ~s\n", [filename:dirname(OutFile)]),
+    execute_overlay(Rest, Vars, BaseDir, TargetDir);
+execute_overlay([{copy, In} | Rest], _Vars, BaseDir, TargetDir) ->
+    execute_overlay([{copy, In, ""} | Rest], _Vars, BaseDir, TargetDir);
+execute_overlay([{copy, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
+    InFile = rebar_templater:render(filename:join(BaseDir, In), Vars),
+    OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
+    case filelib:is_dir(InFile) of
+        true ->
+            ok;
+        false ->
+            ok = filelib:ensure_dir(OutFile)
+    end,
+    rebar_file_utils:cp_r([InFile], OutFile),
+    execute_overlay(Rest, Vars, BaseDir, TargetDir);
+execute_overlay([{template_wildcard, Wildcard, OutDir} | Rest], Vars,
+                BaseDir, TargetDir) ->
+    %% Generate a series of {template, In, Out} instructions from the wildcard
+    %% that will get processed per normal
+    Ifun = fun(F, Acc0) ->
+                   [{template, F,
+                     filename:join(OutDir, filename:basename(F))} | Acc0]
+           end,
+    NewInstrs = lists:foldl(Ifun, Rest, filelib:wildcard(Wildcard, BaseDir)),
+    case length(NewInstrs) =:= length(Rest) of
+        true ->
+            ?WARN("template_wildcard: ~s did not match any files!\n",
+                  [Wildcard]);
+        false ->
+            ok
+    end,
+    ?DEBUG("template_wildcard: ~s expanded to ~p\n", [Wildcard, NewInstrs]),
+    execute_overlay(NewInstrs, Vars, BaseDir, TargetDir);
+execute_overlay([{template, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
+    InFile = rebar_templater:render(filename:join(BaseDir, In), Vars),
+    {ok, InFileData} = file:read_file(InFile),
+    OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
+    ok = filelib:ensure_dir(OutFile),
+    case file:write_file(OutFile, rebar_templater:render(InFileData, Vars)) of
+        ok ->
+            ok = apply_file_info(InFile, OutFile),
+            ?DEBUG("Templated ~p\n", [OutFile]),
+            execute_overlay(Rest, Vars, BaseDir, TargetDir);
+        {error, Reason} ->
+            ?ABORT("Failed to template ~p: ~p\n", [OutFile, Reason])
+    end;
+execute_overlay([{create, Out, Contents} | Rest], Vars, BaseDir, TargetDir) ->
+    OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
+    ok = filelib:ensure_dir(OutFile),
+    case file:write_file(OutFile, Contents) of
+        ok ->
+            ?DEBUG("Created ~p\n", [OutFile]),
+            execute_overlay(Rest, Vars, BaseDir, TargetDir);
+        {error, Reason} ->
+            ?ABORT("Failed to create ~p: ~p\n", [OutFile, Reason])
+    end;
+execute_overlay([{replace, Out, Regex, Replacement} | Rest],
+                Vars, BaseDir, TargetDir) ->
+    execute_overlay([{replace, Out, Regex, Replacement, []} | Rest],
+                    Vars, BaseDir, TargetDir);
+execute_overlay([{replace, Out, Regex, Replacement, Opts} | Rest],
+                Vars, BaseDir, TargetDir) ->
+    Filename = rebar_templater:render(filename:join(TargetDir, Out), Vars),
+    {ok, OrigData} = file:read_file(Filename),
+    Data = re:replace(OrigData, Regex,
+                      rebar_templater:render(Replacement, Vars),
+                      [global, {return, binary}] ++ Opts),
+    case file:write_file(Filename, Data) of
+        ok ->
+            ?DEBUG("Edited ~s: s/~s/~s/\n", [Filename, Regex, Replacement]),
+            execute_overlay(Rest, Vars, BaseDir, TargetDir);
+        {error, Reason} ->
+            ?ABORT("Failed to edit ~p: ~p\n", [Filename, Reason])
+    end;
+execute_overlay([Other | _Rest], _Vars, _BaseDir, _TargetDir) ->
+    {error, {unsupported_operation, Other}}.
+
+
+apply_file_info(InFile, OutFile) ->
+    {ok, FileInfo} = file:read_file_info(InFile),
+    ok = file:write_file_info(OutFile, FileInfo).
+
+create_RELEASES(TargetDir, RelName, RelVsn) ->
+    ReleasesDir = filename:join(TargetDir, "releases"),
+    RelFile = filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
+    Apps = rebar_rel_utils:get_rel_apps(RelFile),
+    TargetLib = filename:join(TargetDir,"lib"),
+
+    AppDirs =
+        [ {App, Vsn, TargetLib}
+          || {App, Vsn} <- Apps,
+             filelib:is_dir(
+               filename:join(TargetLib,
+                             lists:concat([App, "-", Vsn]))) ],
+
+    case release_handler:create_RELEASES(
+           code:root_dir(),
+           ReleasesDir,
+           RelFile,
+           AppDirs) of
+        ok ->
+            ok;
+        {error, Reason} ->
+            ?ABORT("Failed to create RELEASES file: ~p\n",
+                   [Reason])
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_require_vsn.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_require_vsn.erl b/src/rebar/src/rebar_require_vsn.erl
new file mode 100644
index 0000000..385f55c
--- /dev/null
+++ b/src/rebar/src/rebar_require_vsn.erl
@@ -0,0 +1,121 @@
+%% -*- 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_require_vsn).
+
+-include("rebar.hrl").
+
+-export([compile/2,
+         eunit/2]).
+
+%% for internal use only
+-export([info/2]).
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+compile(Config, _) ->
+    check_versions(Config).
+
+eunit(Config, _) ->
+    check_versions(Config).
+
+%% ====================================================================
+%% Internal functions
+%% ====================================================================
+
+info(help, compile) ->
+    info_help();
+info(help, eunit) ->
+    info_help().
+
+info_help() ->
+    ?CONSOLE(
+       "Check required ERTS or OTP release version.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n",
+       [
+        {require_erts_vsn, ".*"},
+        {require_otp_vsn, ".*"},
+        {require_min_otp_vsn, ".*"}
+       ]).
+
+check_versions(Config) ->
+    ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"),
+    ReOpts = [{capture, none}],
+    case re:run(erlang:system_info(version), ErtsRegex, ReOpts) of
+        match ->
+            ?DEBUG("Matched required ERTS version: ~s -> ~s\n",
+                   [erlang:system_info(version), ErtsRegex]);
+        nomatch ->
+            ?ABORT("ERTS version ~s does not match required regex ~s\n",
+                   [erlang:system_info(version), ErtsRegex])
+    end,
+
+    OtpRegex = rebar_config:get(Config, require_otp_vsn, ".*"),
+    case re:run(erlang:system_info(otp_release), OtpRegex, ReOpts) of
+        match ->
+            ?DEBUG("Matched required OTP release: ~s -> ~s\n",
+                   [erlang:system_info(otp_release), OtpRegex]);
+        nomatch ->
+            ?ABORT("OTP release ~s does not match required regex ~s\n",
+                   [erlang:system_info(otp_release), OtpRegex])
+    end,
+
+    case rebar_config:get(Config, require_min_otp_vsn, undefined) of
+        undefined -> ?DEBUG("Min OTP version unconfigured~n", []);
+        MinOtpVsn ->
+            {MinMaj, MinMin} = version_tuple(MinOtpVsn, "configured"),
+            {OtpMaj, OtpMin} = version_tuple(erlang:system_info(otp_release),
+                                             "OTP Release"),
+            case {OtpMaj, OtpMin} >= {MinMaj, MinMin} of
+                true ->
+                    ?DEBUG("~s satisfies the requirement for vsn ~s~n",
+                           [erlang:system_info(otp_release),
+                            MinOtpVsn]);
+                false ->
+                    ?ABORT("OTP release ~s or later is required, you have: ~s~n",
+                           [MinOtpVsn,
+                            erlang:system_info(otp_release)])
+            end
+    end.
+
+version_tuple(OtpRelease, Type) ->
+    case re:run(OtpRelease, "R(\\d+)B?-?(\\d+)?", [{capture, all, list}]) of
+        {match, [_Full, Maj, Min]} ->
+            {list_to_integer(Maj), list_to_integer(Min)};
+        {match, [_Full, Maj]} ->
+            {list_to_integer(Maj), 0};
+        nomatch ->
+            ?ABORT("Cannot parse ~s version string: ~s~n",
+                   [Type, OtpRelease])
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_shell.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_shell.erl b/src/rebar/src/rebar_shell.erl
new file mode 100644
index 0000000..2dbf4a0
--- /dev/null
+++ b/src/rebar/src/rebar_shell.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) 2011 Trifork
+%%
+%% 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_shell).
+-author("Kresten Krab Thorup <kr...@trifork.com>").
+
+-include("rebar.hrl").
+
+-export([shell/2]).
+
+shell(_Config, _AppFile) ->
+    ?CONSOLE("NOTICE: Using experimental 'shell' command~n", []),
+    %% backwards way to say we only want this executed
+    %% for the "top level" directory
+    case is_deps_dir(rebar_utils:get_cwd()) of
+        false ->
+            true = code:add_pathz(rebar_utils:ebin_dir()),
+            user_drv:start(),
+            %% this call never returns (until user quits shell)
+            shell:server(false, false);
+        true ->
+            ok
+    end,
+    ok.
+
+is_deps_dir(Dir) ->
+    case lists:reverse(filename:split(Dir)) of
+        [_, "deps" | _] ->
+            true;
+        _V ->
+            false
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_subdirs.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_subdirs.erl b/src/rebar/src/rebar_subdirs.erl
new file mode 100644
index 0000000..f444a59
--- /dev/null
+++ b/src/rebar/src/rebar_subdirs.erl
@@ -0,0 +1,84 @@
+%% -*- 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_subdirs).
+
+-include("rebar.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export([preprocess/2]).
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+preprocess(Config, _) ->
+    %% Get the list of subdirs specified in the config (if any).
+    Cwd = rebar_utils:get_cwd(),
+    ListSubdirs = rebar_config:get_local(Config, sub_dirs, []),
+    Subdirs0 = lists:flatmap(fun filelib:wildcard/1, ListSubdirs),
+    case {rebar_config:is_skip_dir(Config, Cwd), Subdirs0} of
+        {true, []} ->
+            {ok, []};
+        {true, _} ->
+            ?WARN("Ignoring sub_dirs for ~s~n", [Cwd]),
+            {ok, []};
+        {false, _} ->
+            Check = check_loop(Cwd),
+            ok = lists:foreach(Check, Subdirs0),
+            Subdirs = [filename:join(Cwd, Dir) || Dir <- Subdirs0],
+            {ok, Subdirs}
+    end.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+check_loop(Cwd) ->
+    RebarConfig = filename:join(Cwd, "rebar.config"),
+    fun(Dir0) ->
+            IsSymlink = case file:read_link_info(Dir0) of
+                            {ok, #file_info{type=symlink}} ->
+                                {true, resolve_symlink(Dir0)};
+                            _ ->
+                                {false, Dir0}
+                        end,
+            case IsSymlink of
+                {false, Dir="."} ->
+                    ?ERROR("infinite loop detected:~nsub_dirs"
+                           " entry ~p in ~s~n", [Dir, RebarConfig]);
+                {true, Cwd} ->
+                    ?ERROR("infinite loop detected:~nsub_dirs"
+                           " entry ~p in ~s is a symlink to \".\"~n",
+                           [Dir0, RebarConfig]);
+                _ ->
+                    ok
+            end
+    end.
+
+resolve_symlink(Dir0) ->
+    {ok, Dir} = file:read_link(Dir0),
+    Dir.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_templater.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_templater.erl b/src/rebar/src/rebar_templater.erl
new file mode 100644
index 0000000..b8f7087
--- /dev/null
+++ b/src/rebar/src/rebar_templater.erl
@@ -0,0 +1,462 @@
+%% -*- 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_templater).
+
+-export(['create-app'/2,
+         'create-node'/2,
+         'list-templates'/2,
+         create/2]).
+
+%% API for other utilities that need templating functionality
+-export([resolve_variables/2,
+         render/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+-define(TEMPLATE_RE, ".*\\.template\$").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+'create-app'(Config, _File) ->
+    %% Alias for create w/ template=simpleapp
+    create1(Config, "simpleapp").
+
+'create-node'(Config, _File) ->
+    %% Alias for create w/ template=simplenode
+    create1(Config, "simplenode").
+
+'list-templates'(Config, _File) ->
+    {AvailTemplates, Files} = find_templates(Config),
+    ?DEBUG("Available templates: ~p\n", [AvailTemplates]),
+
+    lists:foreach(
+      fun({Type, F}) ->
+              BaseName = filename:basename(F, ".template"),
+              TemplateTerms = consult(load_file(Files, Type, F)),
+              {_, VarList} = lists:keyfind(variables, 1, TemplateTerms),
+              Vars = lists:foldl(fun({V,_}, Acc) ->
+                                         [atom_to_list(V) | Acc]
+                                 end, [], VarList),
+              ?CONSOLE("  * ~s: ~s (~p) (variables: ~p)\n",
+                       [BaseName, F, Type, string:join(Vars, ", ")])
+      end, AvailTemplates),
+    ok.
+
+create(Config, _) ->
+    TemplateId = template_id(Config),
+    create1(Config, TemplateId).
+
+%%
+%% Given a list of key value pairs, for each string value attempt to
+%% render it using Dict as the context. Storing the result in Dict as Key.
+%%
+resolve_variables([], Dict) ->
+    Dict;
+resolve_variables([{Key, Value0} | Rest], Dict) when is_list(Value0) ->
+    Value = render(list_to_binary(Value0), Dict),
+    resolve_variables(Rest, dict:store(Key, Value, Dict));
+resolve_variables([{Key, {list, Dicts}} | Rest], Dict) when is_list(Dicts) ->
+    %% just un-tag it so mustache can use it
+    resolve_variables(Rest, dict:store(Key, Dicts, Dict));
+resolve_variables([_Pair | Rest], Dict) ->
+    resolve_variables(Rest, Dict).
+
+%%
+%% Render a binary to a string, using mustache and the specified context
+%%
+render(Bin, Context) ->
+    %% Be sure to escape any double-quotes before rendering...
+    ReOpts = [global, {return, list}],
+    Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts),
+    Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts),
+    mustache:render(Str1, Context).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, create) ->
+    ?CONSOLE(
+       "Create skel based on template and vars.~n"
+       "~n"
+       "Valid command line options:~n"
+       "  template= [var=foo,...]~n", []);
+info(help, 'create-app') ->
+    ?CONSOLE(
+       "Create simple app skel.~n"
+       "~n"
+       "Valid command line options:~n"
+       "  [appid=myapp]~n", []);
+info(help, 'create-node') ->
+    ?CONSOLE(
+       "Create simple node skel.~n"
+       "~n"
+       "Valid command line options:~n"
+       "  [nodeid=mynode]~n", []);
+info(help, 'list-templates') ->
+    ?CONSOLE("List available templates.~n", []).
+
+create1(Config, TemplateId) ->
+    {AvailTemplates, Files} = find_templates(Config),
+    ?DEBUG("Available templates: ~p\n", [AvailTemplates]),
+
+    %% Using the specified template id, find the matching template file/type.
+    %% Note that if you define the same template in both ~/.rebar/templates
+    %% that is also present in the escript, the one on the file system will
+    %% be preferred.
+    {Type, Template} = select_template(AvailTemplates, TemplateId),
+
+    %% Load the template definition as is and get the list of variables the
+    %% template requires.
+    TemplateTerms = consult(load_file(Files, Type, Template)),
+    case lists:keyfind(variables, 1, TemplateTerms) of
+        {variables, Vars} ->
+            case parse_vars(Vars, dict:new()) of
+                {error, Entry} ->
+                    Context0 = undefined,
+                    ?ABORT("Failed while processing variables from template ~p."
+                           "Variable definitions must follow form of "
+                           "[{atom(), term()}]. Failed at: ~p\n",
+                           [TemplateId, Entry]);
+                Context0 ->
+                    ok
+            end;
+        false ->
+            ?WARN("No variables section found in template ~p; "
+                  "using empty context.\n", [TemplateId]),
+            Context0 = dict:new()
+    end,
+
+    %% Load variables from disk file, if provided
+    Context1 = case rebar_config:get_global(Config, template_vars, undefined) of
+                   undefined ->
+                       Context0;
+                   File ->
+                       case consult(load_file([], file, File)) of
+                           {error, Reason} ->
+                               ?ABORT("Unable to load template_vars from ~s: ~p\n",
+                                      [File, Reason]);
+                           Terms ->
+                               %% TODO: Cleanup/merge with similar code in rebar_reltool
+                               M = fun(_Key, _Base, Override) -> Override end,
+                               dict:merge(M, Context0, dict:from_list(Terms))
+                       end
+               end,
+
+    %% For each variable, see if it's defined in global vars -- if it is,
+    %% prefer that value over the defaults
+    Context2 = update_vars(Config, dict:fetch_keys(Context1), Context1),
+    ?DEBUG("Template ~p context: ~p\n", [TemplateId, dict:to_list(Context1)]),
+
+    %% Handle variables that possibly include other variables in their
+    %% definition
+    Context = resolve_variables(dict:to_list(Context2), Context2),
+
+    ?DEBUG("Resolved Template ~p context: ~p\n",
+           [TemplateId, dict:to_list(Context)]),
+
+    %% Now, use our context to process the template definition -- this
+    %% permits us to use variables within the definition for filenames.
+    FinalTemplate = consult(render(load_file(Files, Type, Template), Context)),
+    ?DEBUG("Final template def ~p: ~p\n", [TemplateId, FinalTemplate]),
+
+    %% Execute the instructions in the finalized template
+    Force = rebar_config:get_global(Config, force, "0"),
+    execute_template(Files, FinalTemplate, Type, Template, Context, Force, []).
+
+find_templates(Config) ->
+    %% Load a list of all the files in the escript -- cache them since
+    %% we'll potentially need to walk it several times over the course of
+    %% a run.
+    Files = cache_escript_files(Config),
+
+    %% Build a list of available templates
+    AvailTemplates = find_disk_templates(Config)
+        ++ find_escript_templates(Files),
+
+    {AvailTemplates, Files}.
+
+%%
+%% Scan the current escript for available files
+%%
+cache_escript_files(Config) ->
+    {ok, Files} = rebar_utils:escript_foldl(
+                    fun(Name, _, GetBin, Acc) ->
+                            [{Name, GetBin()} | Acc]
+                    end,
+                    [], rebar_config:get_xconf(Config, escript)),
+    Files.
+
+template_id(Config) ->
+    case rebar_config:get_global(Config, template, undefined) of
+        undefined ->
+            ?ABORT("No template specified.\n", []);
+        TemplateId ->
+            TemplateId
+    end.
+
+find_escript_templates(Files) ->
+    [{escript, Name}
+     || {Name, _Bin} <- Files,
+        re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match].
+
+find_disk_templates(Config) ->
+    OtherTemplates = find_other_templates(Config),
+    HomeFiles = rebar_utils:find_files(filename:join([os:getenv("HOME"),
+                                                      ".rebar", "templates"]),
+                                       ?TEMPLATE_RE),
+    LocalFiles = rebar_utils:find_files(".", ?TEMPLATE_RE),
+    [{file, F} || F <- OtherTemplates ++ HomeFiles ++ LocalFiles].
+
+find_other_templates(Config) ->
+    case rebar_config:get_global(Config, template_dir, undefined) of
+        undefined ->
+            [];
+        TemplateDir ->
+            rebar_utils:find_files(TemplateDir, ?TEMPLATE_RE)
+    end.
+
+select_template([], Template) ->
+    ?ABORT("Template ~s not found.\n", [Template]);
+select_template([{Type, Avail} | Rest], Template) ->
+    case filename:basename(Avail, ".template") == Template of
+        true ->
+            {Type, Avail};
+        false ->
+            select_template(Rest, Template)
+    end.
+
+%%
+%% Read the contents of a file from the appropriate source
+%%
+load_file(Files, escript, Name) ->
+    {Name, Bin} = lists:keyfind(Name, 1, Files),
+    Bin;
+load_file(_Files, file, Name) ->
+    {ok, Bin} = file:read_file(Name),
+    Bin.
+
+%%
+%% Parse/validate variables out from the template definition
+%%
+parse_vars([], Dict) ->
+    Dict;
+parse_vars([{Key, Value} | Rest], Dict) when is_atom(Key) ->
+    parse_vars(Rest, dict:store(Key, Value, Dict));
+parse_vars([Other | _Rest], _Dict) ->
+    {error, Other};
+parse_vars(Other, _Dict) ->
+    {error, Other}.
+
+%%
+%% Given a list of keys in Dict, see if there is a corresponding value defined
+%% in the global config; if there is, update the key in Dict with it
+%%
+update_vars(_Config, [], Dict) ->
+    Dict;
+update_vars(Config, [Key | Rest], Dict) ->
+    Value = rebar_config:get_global(Config, Key, dict:fetch(Key, Dict)),
+    update_vars(Config, Rest, dict:store(Key, Value, Dict)).
+
+
+%%
+%% Given a string or binary, parse it into a list of terms, ala file:consult/1
+%%
+consult(Str) when is_list(Str) ->
+    consult([], Str, []);
+consult(Bin) when is_binary(Bin)->
+    consult([], binary_to_list(Bin), []).
+
+consult(Cont, Str, Acc) ->
+    case erl_scan:tokens(Cont, Str, 0) of
+        {done, Result, Remaining} ->
+            case Result of
+                {ok, Tokens, _} ->
+                    {ok, Term} = erl_parse:parse_term(Tokens),
+                    consult([], Remaining, [maybe_dict(Term) | Acc]);
+                {eof, _Other} ->
+                    lists:reverse(Acc);
+                {error, Info, _} ->
+                    {error, Info}
+            end;
+        {more, Cont1} ->
+            consult(Cont1, eof, Acc)
+    end.
+
+
+maybe_dict({Key, {list, Dicts}}) ->
+    %% this is a 'list' element; a list of lists representing dicts
+    {Key, {list, [dict:from_list(D) || D <- Dicts]}};
+maybe_dict(Term) ->
+    Term.
+
+
+write_file(Output, Data, Force) ->
+    %% determine if the target file already exists
+    FileExists = filelib:is_regular(Output),
+
+    %% perform the function if we're allowed,
+    %% otherwise just process the next template
+    case Force =:= "1" orelse FileExists =:= false of
+        true ->
+            ok = filelib:ensure_dir(Output),
+            case {Force, FileExists} of
+                {"1", true} ->
+                    ?CONSOLE("Writing ~s (forcibly overwriting)~n",
+                             [Output]);
+                _ ->
+                    ?CONSOLE("Writing ~s~n", [Output])
+            end,
+            case file:write_file(Output, Data) of
+                ok ->
+                    ok;
+                {error, Reason} ->
+                    ?ABORT("Failed to write output file ~p: ~p\n",
+                           [Output, Reason])
+            end;
+        false ->
+            {error, exists}
+    end.
+
+prepend_instructions(Instructions, Rest) when is_list(Instructions) ->
+    Instructions ++ Rest;
+prepend_instructions(Instruction, Rest) ->
+    [Instruction|Rest].
+
+%%
+%% Execute each instruction in a template definition file.
+%%
+execute_template(_Files, [], _TemplateType, _TemplateName,
+                 _Context, _Force, ExistingFiles) ->
+    case ExistingFiles of
+        [] ->
+            ok;
+        _ ->
+            Msg = lists:flatten([io_lib:format("\t* ~p~n", [F]) ||
+                                    F <- lists:reverse(ExistingFiles)]),
+            Help = "To force overwriting, specify -f/--force/force=1"
+                " on the command line.\n",
+            ?ERROR("One or more files already exist on disk and "
+                   "were not generated:~n~s~s", [Msg , Help])
+    end;
+execute_template(Files, [{'if', Cond, True} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    execute_template(Files, [{'if', Cond, True, []}|Rest], TemplateType,
+                     TemplateName, Context, Force, ExistingFiles);
+execute_template(Files, [{'if', Cond, True, False} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    Instructions = case dict:find(Cond, Context) of
+                       {ok, true} ->
+                           True;
+                       {ok, "true"} ->
+                           True;
+                       _ ->
+                           False
+                   end,
+    execute_template(Files, prepend_instructions(Instructions, Rest),
+                     TemplateType, TemplateName, Context, Force,
+                     ExistingFiles);
+execute_template(Files, [{template, Input, Output} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    InputName = filename:join(filename:dirname(TemplateName), Input),
+    File = load_file(Files, TemplateType, InputName),
+    case write_file(Output, render(File, Context), Force) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles);
+        {error, exists} ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, [Output|ExistingFiles])
+    end;
+execute_template(Files, [{file, Input, Output} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    InputName = filename:join(filename:dirname(TemplateName), Input),
+    File = load_file(Files, TemplateType, InputName),
+    case write_file(Output, File, Force) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles);
+        {error, exists} ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, [Output|ExistingFiles])
+    end;
+execute_template(Files, [{dir, Name} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    case filelib:ensure_dir(filename:join(Name, "dummy")) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles);
+        {error, Reason} ->
+            ?ABORT("Failed while processing template instruction "
+                   "{dir, ~s}: ~p\n", [Name, Reason])
+    end;
+execute_template(Files, [{copy, Input, Output} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    InputName = filename:join(filename:dirname(TemplateName), Input),
+    try rebar_file_utils:cp_r([InputName ++ "/*"], Output) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles)
+    catch _:_ ->
+            ?ABORT("Failed while processing template instruction "
+                   "{copy, ~s, ~s}~n", [Input, Output])
+    end;
+execute_template(Files, [{chmod, Mod, File} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles)
+  when is_integer(Mod) ->
+    case file:change_mode(File, Mod) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles);
+        {error, Reason} ->
+            ?ABORT("Failed while processing template instruction "
+                   "{chmod, ~b, ~s}: ~p~n", [Mod, File, Reason])
+    end;
+execute_template(Files, [{symlink, Existing, New} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    case file:make_symlink(Existing, New) of
+        ok ->
+            execute_template(Files, Rest, TemplateType, TemplateName,
+                             Context, Force, ExistingFiles);
+        {error, Reason} ->
+            ?ABORT("Failed while processing template instruction "
+                   "{symlink, ~s, ~s}: ~p~n", [Existing, New, Reason])
+    end;
+execute_template(Files, [{variables, _} | Rest], TemplateType,
+                 TemplateName, Context, Force, ExistingFiles) ->
+    execute_template(Files, Rest, TemplateType, TemplateName,
+                     Context, Force, ExistingFiles);
+execute_template(Files, [Other | Rest], TemplateType, TemplateName,
+                 Context, Force, ExistingFiles) ->
+    ?WARN("Skipping unknown template instruction: ~p\n", [Other]),
+    execute_template(Files, Rest, TemplateType, TemplateName, Context,
+                     Force, ExistingFiles).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_upgrade.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_upgrade.erl b/src/rebar/src/rebar_upgrade.erl
new file mode 100644
index 0000000..5814e51
--- /dev/null
+++ b/src/rebar/src/rebar_upgrade.erl
@@ -0,0 +1,268 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2011 Joe Williams (joe@joetify.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+
+-module(rebar_upgrade).
+
+-include("rebar.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export(['generate-upgrade'/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-define(TMP, "_tmp").
+
+%% ====================================================================
+%% Public API
+%% ====================================================================
+
+'generate-upgrade'(Config0, ReltoolFile) ->
+    %% Get the old release path
+    {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile),
+    TargetParentDir = rebar_rel_utils:get_target_parent_dir(Config,
+                                                            ReltoolConfig),
+    TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
+
+    PrevRelPath = rebar_rel_utils:get_previous_release_path(Config),
+    OldVerPath = filename:join([TargetParentDir, PrevRelPath]),
+
+    %% Run checks to make sure that building a package is possible
+    {NewVerPath, NewName, NewVer} = run_checks(Config, OldVerPath,
+                                               ReltoolConfig),
+    NameVer = NewName ++ "_" ++ NewVer,
+
+    %% Save the code path prior to doing anything
+    OrigPath = code:get_path(),
+
+    %% Prepare the environment for building the package
+    ok = setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer),
+
+    %% Build the package
+    run_systools(NameVer, NewName),
+
+    %% Boot file changes
+    {ok, _} = boot_files(TargetDir, NewVer, NewName),
+
+    %% Extract upgrade and tar it back up with changes
+    make_tar(NameVer, NewVer, NewName),
+
+    %% Clean up files that systools created
+    ok = cleanup(NameVer),
+
+    %% Restore original path
+    true = code:set_path(OrigPath),
+
+    {ok, Config}.
+
+%% ===================================================================
+%% Internal functions
+%% ==================================================================
+
+info(help, 'generate-upgrade') ->
+    ?CONSOLE("Build an upgrade package.~n"
+             "~n"
+             "Valid command line options:~n"
+             "  previous_release=path~n",
+             []).
+
+run_checks(Config, OldVerPath, ReltoolConfig) ->
+    true = rebar_utils:prop_check(filelib:is_dir(OldVerPath),
+                                  "Release directory doesn't exist (~p)~n",
+                                  [OldVerPath]),
+
+    {Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
+
+    NewVerPath =
+        filename:join(
+          [rebar_rel_utils:get_target_parent_dir(Config, ReltoolConfig),
+           Name]),
+    true = rebar_utils:prop_check(filelib:is_dir(NewVerPath),
+                                  "Release directory doesn't exist (~p)~n",
+                                  [NewVerPath]),
+
+    {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath),
+    {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath),
+
+    true =
+        rebar_utils:prop_check(NewName == OldName,
+                               "New and old .rel release names do not match~n",
+                               []),
+    true =
+        rebar_utils:prop_check(Name == NewName,
+                               "Reltool and .rel release names do not match~n",
+                               []),
+    true =
+        rebar_utils:prop_check(NewVer =/= OldVer,
+                               "New and old .rel contain the same version~n",
+                               []),
+    true =
+        rebar_utils:prop_check(Ver == NewVer,
+                               "Reltool and .rel versions do not match~n", []),
+
+    {NewVerPath, NewName, NewVer}.
+
+setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer) ->
+    Src = filename:join([NewVerPath, "releases",
+                         NewVer, NewName ++ ".rel"]),
+    Dst = filename:join([".", NameVer ++ ".rel"]),
+    {ok, _} = file:copy(Src, Dst),
+    ok = code:add_pathsa(
+           lists:append([
+                         filelib:wildcard(filename:join([NewVerPath,
+                                                         "lib", "*", "ebin"])),
+                         filelib:wildcard(filename:join([OldVerPath,
+                                                         "releases", "*"])),
+                         filelib:wildcard(filename:join([OldVerPath,
+                                                         "lib", "*", "ebin"]))
+                        ])).
+
+run_systools(NewVer, Name) ->
+    Opts = [silent],
+    NameList = [Name],
+    case systools:make_relup(NewVer, NameList, NameList, Opts) of
+        {error, _, Msg} ->
+            ?ABORT("Systools [systools:make_relup/4] aborted with: ~p~n",
+                   [Msg]);
+        _ ->
+            ?DEBUG("Relup created~n", []),
+            case systools:make_script(NewVer, Opts) of
+                {error, _, Msg1} ->
+                    ?ABORT("Systools [systools:make_script/2] "
+                           "aborted with: ~p~n", [Msg1]);
+                _ ->
+                    ?DEBUG("Script created~n", []),
+                    case systools:make_tar(NewVer, Opts) of
+                        {error, _, Msg2} ->
+                            ?ABORT("Systools [systools:make_tar/2] "
+                                   "aborted with: ~p~n", [Msg2]);
+                        _ ->
+                            ?DEBUG("Tarball created~n", []),
+                            ok
+                    end
+            end
+    end.
+
+boot_files(TargetDir, Ver, Name) ->
+    ok = file:make_dir(filename:join([".", ?TMP])),
+    ok = file:make_dir(filename:join([".", ?TMP, "releases"])),
+    ok = file:make_dir(filename:join([".", ?TMP, "releases", Ver])),
+    case os:type() of
+        {win32,_} ->
+            ok;
+        _ ->
+            ok = file:make_symlink(
+                   filename:join(["start.boot"]),
+                   filename:join([".", ?TMP, "releases", Ver, Name ++ ".boot"]))
+    end,
+    {ok, _} =
+        file:copy(
+          filename:join([TargetDir, "releases", Ver, "start_clean.boot"]),
+          filename:join([".", ?TMP, "releases", Ver, "start_clean.boot"])),
+
+    SysConfig = filename:join([TargetDir, "releases", Ver, "sys.config"]),
+    _ = case filelib:is_regular(SysConfig) of
+            true ->
+                {ok, _} = file:copy(
+                            SysConfig,
+                            filename:join([".", ?TMP, "releases", Ver,
+                                           "sys.config"]));
+            false -> ok
+        end,
+
+    VmArgs = filename:join([TargetDir, "releases", Ver, "vm.args"]),
+    case filelib:is_regular(VmArgs) of
+        true ->
+            {ok, _} = file:copy(
+                        VmArgs,
+                        filename:join([".", ?TMP, "releases", Ver, "vm.args"]));
+        false -> {ok, 0}
+    end.
+
+make_tar(NameVer, NewVer, NewName) ->
+    Filename = NameVer ++ ".tar.gz",
+    {ok, Cwd} = file:get_cwd(),
+    Absname = filename:join([Cwd, Filename]),
+    ok = file:set_cwd(?TMP),
+    ok = erl_tar:extract(Absname, [compressed]),
+    ok = file:delete(Absname),
+    case os:type() of
+        {win32,_} ->
+            {ok, _} =
+                file:copy(
+                  filename:join([".", "releases", NewVer, "start.boot"]),
+                  filename:join([".", "releases", NewVer, NewName ++ ".boot"])),
+            ok;
+        _ ->
+            ok
+    end,
+    {ok, Tar} = erl_tar:open(Absname, [write, compressed]),
+    ok = erl_tar:add(Tar, "lib", []),
+    ok = erl_tar:add(Tar, "releases", []),
+    ok = erl_tar:close(Tar),
+    ok = file:set_cwd(Cwd),
+    ?CONSOLE("~s upgrade package created~n", [NameVer]).
+
+cleanup(NameVer) ->
+    ?DEBUG("Removing files needed for building the upgrade~n", []),
+    Files = [
+             filename:join([".", NameVer ++ ".rel"]),
+             filename:join([".", NameVer ++ ".boot"]),
+             filename:join([".", NameVer ++ ".script"]),
+             filename:join([".", "relup"])
+            ],
+    lists:foreach(fun(F) -> ok = file:delete(F) end, Files),
+
+    ok = remove_dir_tree(?TMP).
+
+%% adapted from http://www.erlang.org/doc/system_principles/create_target.html
+remove_dir_tree(Dir) ->
+    remove_all_files(".", [Dir]).
+remove_all_files(Dir, Files) ->
+    lists:foreach(fun(File) ->
+                          FilePath = filename:join([Dir, File]),
+                          {ok, FileInfo, Link} = file_info(FilePath),
+                          case {Link, FileInfo#file_info.type} of
+                              {false, directory} ->
+                                  {ok, DirFiles} = file:list_dir(FilePath),
+                                  remove_all_files(FilePath, DirFiles),
+                                  file:del_dir(FilePath);
+                              _ ->
+                                  file:delete(FilePath)
+                          end
+                  end, Files).
+
+file_info(Path) ->
+    case file:read_file_info(Path) of
+        {ok, Info} ->
+            {ok, Info, false};
+        {error, enoent} ->
+            {ok, Info} = file:read_link_info(Path),
+            {ok, Info, true};
+        Error ->
+            Error
+    end.