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.