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

[05/10] Import rebar

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_deps.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_deps.erl b/src/rebar/src/rebar_deps.erl
new file mode 100644
index 0000000..2e305d5
--- /dev/null
+++ b/src/rebar/src/rebar_deps.erl
@@ -0,0 +1,774 @@
+%% -*- 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_deps).
+
+-include("rebar.hrl").
+
+-export([preprocess/2,
+         postprocess/2,
+         compile/2,
+         setup_env/1,
+         'check-deps'/2,
+         'get-deps'/2,
+         'update-deps'/2,
+         'delete-deps'/2,
+         'list-deps'/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-record(dep, { dir,
+               app,
+               vsn_regex,
+               source,
+               is_raw }). %% is_raw = true means non-Erlang/OTP dependency
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+preprocess(Config, _) ->
+    %% Side effect to set deps_dir globally for all dependencies from
+    %% top level down. Means the root deps_dir is honoured or the default
+    %% used globally since it will be set on the first time through here
+    Config1 = set_shared_deps_dir(Config, get_shared_deps_dir(Config, [])),
+
+    %% Get the list of deps for the current working directory and identify those
+    %% deps that are available/present.
+    Deps = rebar_config:get_local(Config1, deps, []),
+    {Config2, {AvailableDeps, MissingDeps}} = find_deps(Config1, find, Deps),
+
+    ?DEBUG("Available deps: ~p\n", [AvailableDeps]),
+    ?DEBUG("Missing deps  : ~p\n", [MissingDeps]),
+
+    %% Add available deps to code path
+    Config3 = update_deps_code_path(Config2, AvailableDeps),
+
+    %% Filtering out 'raw' dependencies so that no commands other than
+    %% deps-related can be executed on their directories.
+    NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw],
+
+    case rebar_config:get_xconf(Config, current_command, undefined) of
+        'update-deps' ->
+            %% Skip ALL of the dep folders, we do this because we don't want
+            %% any other calls to preprocess() for update-deps beyond the
+            %% toplevel directory. They aren't actually harmful, but they slow
+            %% things down unnecessarily.
+            NewConfig = lists:foldl(
+                          fun(D, Acc) ->
+                                  rebar_config:set_skip_dir(Acc, D#dep.dir)
+                          end,
+                          Config3,
+                          collect_deps(rebar_utils:get_cwd(), Config3)),
+            %% Return the empty list, as we don't want anything processed before
+            %% us.
+            {ok, NewConfig, []};
+        _ ->
+            %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core
+            %% so that the current command doesn't run on the dep dir.
+            %% However, pre/postprocess WILL run (and we want it to) for
+            %% transitivity purposes.
+            %%
+            %% Also, if skip_deps=comma,separated,app,list, then only the given
+            %% dependencies are skipped.
+            NewConfig =
+                case rebar_config:get_global(Config3, skip_deps, false) of
+                    "true" ->
+                        lists:foldl(
+                          fun(#dep{dir = Dir}, C) ->
+                                  rebar_config:set_skip_dir(C, Dir)
+                          end, Config3, AvailableDeps);
+                    Apps when is_list(Apps) ->
+                        SkipApps = [list_to_atom(App) ||
+                                       App <- string:tokens(Apps, ",")],
+                        lists:foldl(
+                          fun(#dep{dir = Dir, app = App}, C) ->
+                                  case lists:member(App, SkipApps) of
+                                      true -> rebar_config:set_skip_dir(C, Dir);
+                                      false -> C
+                                  end
+                          end, Config3, AvailableDeps);
+                    _ ->
+                        Config3
+                end,
+
+            %% Return all the available dep directories for process
+            {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}
+    end.
+
+postprocess(Config, _) ->
+    case rebar_config:get_xconf(Config, ?MODULE, undefined) of
+        undefined ->
+            {ok, []};
+        Dirs ->
+            NewConfig = rebar_config:erase_xconf(Config, ?MODULE),
+            {ok, NewConfig, Dirs}
+    end.
+
+compile(Config, _) ->
+    {Config1, _AvailDeps} = do_check_deps(Config),
+    {ok, Config1}.
+
+%% set REBAR_DEPS_DIR and ERL_LIBS environment variables
+setup_env(Config) ->
+    {true, DepsDir} = get_deps_dir(Config),
+    %% include rebar's DepsDir in ERL_LIBS
+    Separator = case os:type() of
+                    {win32, nt} ->
+                        ";";
+                    _ ->
+                        ":"
+                end,
+    ERL_LIBS = case os:getenv("ERL_LIBS") of
+                   false ->
+                       {"ERL_LIBS", DepsDir};
+                   PrevValue ->
+                       {"ERL_LIBS", DepsDir ++ Separator ++ PrevValue}
+               end,
+    [{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS].
+
+%% common function used by 'check-deps' and 'compile'
+do_check_deps(Config) ->
+    %% Get the list of immediate (i.e. non-transitive) deps that are missing
+    Deps = rebar_config:get_local(Config, deps, []),
+    case find_deps(Config, find, Deps) of
+        {Config1, {AvailDeps, []}} ->
+            %% No missing deps
+            {Config1, AvailDeps};
+        {_Config1, {_, MissingDeps}} ->
+            lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) ->
+                                  ?CONSOLE("Dependency not available: "
+                                           "~p-~s (~p)\n", [App, Vsn, Src])
+                          end, MissingDeps),
+            ?FAIL
+    end.
+
+'check-deps'(Config, _) ->
+    {Config1, AvailDeps} = do_check_deps(Config),
+    {ok, save_dep_dirs(Config1, AvailDeps)}.
+
+'get-deps'(Config, _) ->
+    %% Determine what deps are available and missing
+    Deps = rebar_config:get_local(Config, deps, []),
+    {Config1, {_AvailableDeps, MissingDeps}} = find_deps(Config, find, Deps),
+    MissingDeps1 = [D || D <- MissingDeps, D#dep.source =/= undefined],
+
+    %% For each missing dep with a specified source, try to pull it.
+    {Config2, PulledDeps} =
+        lists:foldl(fun(D, {C, PulledDeps0}) ->
+                            {C1, D1} = use_source(C, D),
+                            {C1, [D1 | PulledDeps0]}
+                    end, {Config1, []}, MissingDeps1),
+
+    %% Add each pulled dep to our list of dirs for post-processing. This yields
+    %% the necessary transitivity of the deps
+    {ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}.
+
+'update-deps'(Config, _) ->
+    Config1 = rebar_config:set_xconf(Config, depowner, dict:new()),
+    {Config2, UpdatedDeps} = update_deps_int(Config1, []),
+    DepOwners = rebar_config:get_xconf(Config2, depowner, dict:new()),
+
+    %% check for conflicting deps
+    _ = [?ERROR("Conflicting dependencies for ~p: ~p~n",
+                [K, [{"From: " ++ string:join(dict:fetch(D, DepOwners), ", "),
+                      {D#dep.vsn_regex, D#dep.source}} || D <- V]])
+         || {K, V} <- dict:to_list(
+                        lists:foldl(
+                          fun(Dep, Acc) ->
+                                  dict:append(Dep#dep.app, Dep, Acc)
+                          end, dict:new(), UpdatedDeps)),
+            length(V) > 1],
+
+    %% Add each updated dep to our list of dirs for post-processing. This yields
+    %% the necessary transitivity of the deps
+    {ok, save_dep_dirs(Config, UpdatedDeps)}.
+
+'delete-deps'(Config, _) ->
+    %% Delete all the available deps in our deps/ directory, if any
+    {true, DepsDir} = get_deps_dir(Config),
+    Deps = rebar_config:get_local(Config, deps, []),
+    {Config1, {AvailableDeps, _}} = find_deps(Config, find, Deps),
+    _ = [delete_dep(D)
+         || D <- AvailableDeps,
+            lists:prefix(DepsDir, D#dep.dir)],
+    {ok, Config1}.
+
+'list-deps'(Config, _) ->
+    Deps = rebar_config:get_local(Config, deps, []),
+    case find_deps(Config, find, Deps) of
+        {Config1, {AvailDeps, []}} ->
+            lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps),
+            {ok, save_dep_dirs(Config1, AvailDeps)};
+        {_, MissingDeps} ->
+            ?ABORT("Missing dependencies: ~p\n", [MissingDeps])
+    end.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+    info_help("Display to be fetched dependencies");
+info(help, 'check-deps') ->
+    info_help("Display to be fetched dependencies");
+info(help, 'get-deps') ->
+    info_help("Fetch dependencies");
+info(help, 'update-deps') ->
+    info_help("Update fetched dependencies");
+info(help, 'delete-deps') ->
+    info_help("Delete fetched dependencies");
+info(help, 'list-deps') ->
+    info_help("List dependencies").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~p~n"
+       "  ~p~n"
+       "Valid command line options:~n"
+       "  deps_dir=\"deps\" (override default or rebar.config deps_dir)~n",
+       [
+        Description,
+        {deps_dir, "deps"},
+        {deps,
+         [app_name,
+          {rebar, "1.0.*"},
+          {rebar, ".*",
+           {git, "git://github.com/rebar/rebar.git"}},
+          {rebar, ".*",
+           {git, "git://github.com/rebar/rebar.git", "Rev"}},
+          {rebar, "1.0.*",
+           {git, "git://github.com/rebar/rebar.git", {branch, "master"}}},
+          {rebar, "1.0.0",
+           {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}},
+          {rebar, "",
+           {git, "git://github.com/rebar/rebar.git", {branch, "master"}},
+           [raw]},
+          {app_name, ".*", {hg, "https://www.example.org/url"}},
+          {app_name, ".*", {rsync, "Url"}},
+          {app_name, ".*", {svn, "https://www.example.org/url"}},
+          {app_name, ".*", {svn, "svn://svn.example.org/url"}},
+          {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}},
+          {app_name, ".*", {fossil, "https://www.example.org/url"}},
+          {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}]}
+       ]).
+
+%% Added because of trans deps,
+%% need all deps in same dir and should be the one set by the root rebar.config
+%% In case one is given globally, it has higher priority
+%% Sets a default if root config has no deps_dir set
+set_shared_deps_dir(Config, []) ->
+    LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"),
+    GlobalDepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir),
+    DepsDir = case os:getenv("REBAR_DEPS_DIR") of
+                  false ->
+                      GlobalDepsDir;
+                  Dir ->
+                      Dir
+              end,
+    rebar_config:set_xconf(Config, deps_dir, DepsDir);
+set_shared_deps_dir(Config, _DepsDir) ->
+    Config.
+
+get_shared_deps_dir(Config, Default) ->
+    rebar_config:get_xconf(Config, deps_dir, Default).
+
+get_deps_dir(Config) ->
+    get_deps_dir(Config, "").
+
+get_deps_dir(Config, App) ->
+    BaseDir = rebar_config:get_xconf(Config, base_dir, []),
+    DepsDir = get_shared_deps_dir(Config, "deps"),
+    {true, filename:join([BaseDir, DepsDir, App])}.
+
+dep_dirs(Deps) ->
+    [D#dep.dir || D <- Deps].
+
+save_dep_dirs(Config, Deps) ->
+    rebar_config:set_xconf(Config, ?MODULE, dep_dirs(Deps)).
+
+get_lib_dir(App) ->
+    %% Find App amongst the reachable lib directories
+    %% Returns either the found path or a tagged tuple with a boolean
+    %% to match get_deps_dir's return type
+    case code:lib_dir(App) of
+        {error, bad_name} -> {false, bad_name};
+        Path -> {true, Path}
+    end.
+
+update_deps_code_path(Config, []) ->
+    Config;
+update_deps_code_path(Config, [Dep | Rest]) ->
+    Config2 =
+        case is_app_available(Config, Dep#dep.app,
+                              Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
+            {Config1, {true, _}} ->
+                Dir = filename:join(Dep#dep.dir, "ebin"),
+                ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
+                ?DEBUG("Adding ~s to code path~n", [Dir]),
+                true = code:add_patha(Dir),
+                Config1;
+            {Config1, {false, _}} ->
+                Config1
+        end,
+    update_deps_code_path(Config2, Rest).
+
+find_deps(Config, find=Mode, Deps) ->
+    find_deps(Config, Mode, Deps, {[], []});
+find_deps(Config, read=Mode, Deps) ->
+    find_deps(Config, Mode, Deps, []).
+
+find_deps(Config, find, [], {Avail, Missing}) ->
+    {Config, {lists:reverse(Avail), lists:reverse(Missing)}};
+find_deps(Config, read, [], Deps) ->
+    {Config, lists:reverse(Deps)};
+find_deps(Config, Mode, [App | Rest], Acc) when is_atom(App) ->
+    find_deps(Config, Mode, [{App, ".*", undefined} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
+    find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
+    find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc)
+  when is_list(Opts) ->
+    Dep = #dep { app = App,
+                 vsn_regex = VsnRegex,
+                 source = Source,
+                 %% dependency is considered raw (i.e. non-Erlang/OTP) when
+                 %% 'raw' option is present
+                 is_raw = proplists:get_value(raw, Opts, false) },
+    {Config1, {Availability, FoundDir}} = find_dep(Config, Dep),
+    find_deps(Config1, Mode, Rest,
+              acc_deps(Mode, Availability, Dep, FoundDir, Acc));
+find_deps(_Config, _Mode, [Other | _Rest], _Acc) ->
+    ?ABORT("Invalid dependency specification ~p in ~s\n",
+           [Other, rebar_utils:get_cwd()]).
+
+find_dep(Config, Dep) ->
+    %% Find a dep based on its source,
+    %% e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"}
+    %% Deps with a source must be found (or fetched) locally.
+    %% Those without a source may be satisfied from lib dir (get_lib_dir).
+    find_dep(Config, Dep, Dep#dep.source).
+
+find_dep(Config, Dep, undefined) ->
+    %% 'source' is undefined.  If Dep is not satisfied locally,
+    %% go ahead and find it amongst the lib_dir's.
+    case find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)) of
+        {_Config1, {avail, _Dir}} = Avail ->
+            Avail;
+        {Config1, {missing, _}} ->
+            find_dep_in_dir(Config1, Dep, get_lib_dir(Dep#dep.app))
+    end;
+find_dep(Config, Dep, _Source) ->
+    %% _Source is defined.  Regardless of what it is, we must find it
+    %% locally satisfied or fetch it from the original source
+    %% into the project's deps
+    find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)).
+
+find_dep_in_dir(Config, _Dep, {false, Dir}) ->
+    {Config, {missing, Dir}};
+find_dep_in_dir(Config, Dep, {true, Dir}) ->
+    App = Dep#dep.app,
+    VsnRegex = Dep#dep.vsn_regex,
+    IsRaw = Dep#dep.is_raw,
+    case is_app_available(Config, App, VsnRegex, Dir, IsRaw) of
+        {Config1, {true, _AppFile}} -> {Config1, {avail, Dir}};
+        {Config1, {false, _}}       -> {Config1, {missing, Dir}}
+    end.
+
+acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) ->
+    {[Dep#dep { dir = AppDir } | Avail], Missing};
+acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) ->
+    {Avail, [Dep#dep { dir = AppDir } | Missing]};
+acc_deps(read, _, Dep, AppDir, Acc) ->
+    [Dep#dep { dir = AppDir } | Acc].
+
+delete_dep(D) ->
+    case filelib:is_dir(D#dep.dir) of
+        true ->
+            ?INFO("Deleting dependency: ~s\n", [D#dep.dir]),
+            rebar_file_utils:rm_rf(D#dep.dir);
+        false ->
+            ok
+    end.
+
+require_source_engine(Source) ->
+    true = source_engine_avail(Source),
+    ok.
+
+%% IsRaw = false means regular Erlang/OTP dependency
+%%
+%% IsRaw = true means non-Erlang/OTP dependency, e.g. the one that does not
+%% have a proper .app file
+is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) ->
+    ?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]),
+    case rebar_app_utils:is_app_dir(Path) of
+        {true, AppFile} ->
+            case rebar_app_utils:app_name(Config, AppFile) of
+                {Config1, App} ->
+                    {Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppFile),
+                    ?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n",
+                          [App, VsnRegex, App, Vsn, Path]),
+                    case re:run(Vsn, VsnRegex, [{capture, none}]) of
+                        match ->
+                            {Config2, {true, Path}};
+                        nomatch ->
+                            ?WARN("~s has version ~p; requested regex was ~s\n",
+                                  [AppFile, Vsn, VsnRegex]),
+                            {Config2,
+                             {false, {version_mismatch,
+                                      {AppFile,
+                                       {expected, VsnRegex}, {has, Vsn}}}}}
+                    end;
+                {Config1, OtherApp} ->
+                    ?WARN("~s has application id ~p; expected ~p\n",
+                          [AppFile, OtherApp, App]),
+                    {Config1,
+                     {false, {name_mismatch,
+                              {AppFile, {expected, App}, {has, OtherApp}}}}}
+            end;
+        false ->
+            ?WARN("Expected ~s to be an app dir (containing ebin/*.app), "
+                  "but no .app found.\n", [Path]),
+            {Config, {false, {missing_app_file, Path}}}
+    end;
+is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) ->
+    ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n",
+           [App, Path]),
+    case filelib:is_dir(Path) of
+        true ->
+            %% TODO: look for version string in <Path>/VERSION file? Not clear
+            %% how to detect git/svn/hg/{cmd, ...} settings that can be passed
+            %% to rebar_utils:vcs_vsn/2 to obtain version dynamically
+            {Config, {true, Path}};
+        false ->
+            ?WARN("Expected ~s to be a raw dependency directory, "
+                  "but no directory found.\n", [Path]),
+            {Config, {false, {missing_raw_dependency_directory, Path}}}
+    end.
+
+use_source(Config, Dep) ->
+    use_source(Config, Dep, 3).
+
+use_source(_Config, Dep, 0) ->
+    ?ABORT("Failed to acquire source from ~p after 3 tries.\n",
+           [Dep#dep.source]);
+use_source(Config, Dep, Count) ->
+    case filelib:is_dir(Dep#dep.dir) of
+        true ->
+            %% Already downloaded -- verify the versioning matches the regex
+            case is_app_available(Config, Dep#dep.app, Dep#dep.vsn_regex,
+                                  Dep#dep.dir, Dep#dep.is_raw) of
+                {Config1, {true, _}} ->
+                    Dir = filename:join(Dep#dep.dir, "ebin"),
+                    ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
+                    %% Available version matches up -- we're good to go;
+                    %% add the app dir to our code path
+                    true = code:add_patha(Dir),
+                    {Config1, Dep};
+                {_Config1, {false, Reason}} ->
+                    %% The app that was downloaded doesn't match up (or had
+                    %% errors or something). For the time being, abort.
+                    ?ABORT("Dependency dir ~s failed application validation "
+                           "with reason:~n~p.\n", [Dep#dep.dir, Reason])
+            end;
+        false ->
+            ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
+            require_source_engine(Dep#dep.source),
+            {true, TargetDir} = get_deps_dir(Config, Dep#dep.app),
+            download_source(TargetDir, Dep#dep.source),
+            use_source(Config, Dep#dep { dir = TargetDir }, Count-1)
+    end.
+
+download_source(AppDir, {hg, Url, Rev}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]),
+    rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]);
+download_source(AppDir, {git, Url}) ->
+    download_source(AppDir, {git, Url, {branch, "HEAD"}});
+download_source(AppDir, {git, Url, ""}) ->
+    download_source(AppDir, {git, Url, {branch, "HEAD"}});
+download_source(AppDir, {git, Url, {branch, Branch}}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]),
+    rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]);
+download_source(AppDir, {git, Url, {tag, Tag}}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]),
+    rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]);
+download_source(AppDir, {git, Url, Rev}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]),
+    rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, AppDir}]);
+download_source(AppDir, {bzr, Url, Rev}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s",
+                        [Rev, Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]);
+download_source(AppDir, {svn, Url, Rev}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s",
+                        [Rev, Url, filename:basename(AppDir)]),
+                   [{cd, filename:dirname(AppDir)}]);
+download_source(AppDir, {rsync, Url}) ->
+    ok = filelib:ensure_dir(AppDir),
+    rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []);
+download_source(AppDir, {fossil, Url}) ->
+    download_source(AppDir, {fossil, Url, ""});
+download_source(AppDir, {fossil, Url, Version}) ->
+    Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"),
+    ok = filelib:ensure_dir(Repository),
+    ok = file:set_cwd(AppDir),
+    rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]),
+                   [{cd, AppDir}]),
+    rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]),
+                   []).
+
+update_source(Config, Dep) ->
+    %% It's possible when updating a source, that a given dep does not have a
+    %% VCS directory, such as when a source archive is built of a project, with
+    %% all deps already downloaded/included. So, verify that the necessary VCS
+    %% directory exists before attempting to do the update.
+    {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
+    case has_vcs_dir(element(1, Dep#dep.source), AppDir) of
+        true ->
+            ?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
+            require_source_engine(Dep#dep.source),
+            update_source1(AppDir, Dep#dep.source),
+            Dep;
+        false ->
+            ?WARN("Skipping update for ~p: "
+                  "no VCS directory available!\n", [Dep]),
+            Dep
+    end.
+
+update_source1(AppDir, {git, Url}) ->
+    update_source1(AppDir, {git, Url, {branch, "HEAD"}});
+update_source1(AppDir, {git, Url, ""}) ->
+    update_source1(AppDir, {git, Url, {branch, "HEAD"}});
+update_source1(AppDir, {git, _Url, {branch, Branch}}) ->
+    ShOpts = [{cd, AppDir}],
+    rebar_utils:sh("git fetch origin", ShOpts),
+    rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts),
+    rebar_utils:sh(
+      ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts);
+update_source1(AppDir, {git, _Url, {tag, Tag}}) ->
+    ShOpts = [{cd, AppDir}],
+    rebar_utils:sh("git fetch origin", ShOpts),
+    rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts);
+update_source1(AppDir, {git, _Url, Refspec}) ->
+    ShOpts = [{cd, AppDir}],
+    rebar_utils:sh("git fetch origin", ShOpts),
+    rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts);
+update_source1(AppDir, {svn, _Url, Rev}) ->
+    rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]);
+update_source1(AppDir, {hg, _Url, Rev}) ->
+    rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]);
+update_source1(AppDir, {bzr, _Url, Rev}) ->
+    rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]);
+update_source1(AppDir, {rsync, Url}) ->
+    rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]);
+update_source1(AppDir, {fossil, Url}) ->
+    update_source1(AppDir, {fossil, Url, ""});
+update_source1(AppDir, {fossil, _Url, Version}) ->
+    ok = file:set_cwd(AppDir),
+    rebar_utils:sh("fossil pull", [{cd, AppDir}]),
+    rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).
+
+%% Recursively update deps, this is not done via rebar's usual dep traversal as
+%% that is the wrong order (tips are updated before branches). Instead we do a
+%% traverse the deps at each level completely before traversing *their* deps.
+%% This allows updates to actually propogate down the tree, rather than fail to
+%% flow up the tree, which was the previous behaviour.
+update_deps_int(Config0, UDD) ->
+    %% Determine what deps are required
+    ConfDir = filename:basename(rebar_utils:get_cwd()),
+    RawDeps = rebar_config:get_local(Config0, deps, []),
+    {Config1, Deps} = find_deps(Config0, read, RawDeps),
+
+    %% Update each dep
+    UpdatedDeps = [update_source(Config1, D)
+                   || D <- Deps, D#dep.source =/= undefined,
+                      not lists:member(D, UDD),
+                      not should_skip_update_dep(Config1, D)
+                  ],
+
+    lists:foldl(fun(Dep, {Config, Updated}) ->
+                        {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
+                        Config2 = case has_vcs_dir(element(1, Dep#dep.source),
+                                                   AppDir) of
+                                      false ->
+                                          %% If the dep did not exist (maybe it
+                                          %% was added), clone it.
+                                          %% We'll traverse ITS deps below and
+                                          %% clone them if needed.
+                                          {C1, _D1} = use_source(Config, Dep),
+                                          C1;
+                                      true ->
+                                          Config
+                                  end,
+                        ok = file:set_cwd(AppDir),
+                        Config3 = rebar_config:new(Config2),
+                        %% track where a dep comes from...
+                        DepOwner = dict:append(
+                                     Dep, ConfDir,
+                                     rebar_config:get_xconf(Config3, depowner,
+                                                            dict:new())),
+                        Config4 = rebar_config:set_xconf(Config3, depowner,
+                                                         DepOwner),
+
+                        {Config5, Res} = update_deps_int(Config4, Updated),
+                        {Config5, lists:umerge(lists:sort(Res),
+                                               lists:sort(Updated))}
+                end, {Config1, lists:umerge(lists:sort(UpdatedDeps),
+                                            lists:sort(UDD))}, UpdatedDeps).
+
+should_skip_update_dep(Config, Dep) ->
+    {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
+    case rebar_app_utils:is_app_dir(AppDir) of
+        false ->
+            false;
+        {true, AppFile} ->
+            case rebar_app_utils:is_skipped_app(Config, AppFile) of
+                {_Config, {true, _SkippedApp}} ->
+                    true;
+                _ ->
+                    false
+            end
+    end.
+
+%% Recursively walk the deps and build a list of them.
+collect_deps(Dir, C) ->
+    case file:set_cwd(Dir) of
+        ok ->
+            Config = rebar_config:new(C),
+            RawDeps = rebar_config:get_local(Config, deps, []),
+            {Config1, Deps} = find_deps(Config, read, RawDeps),
+
+            lists:flatten(Deps ++ [begin
+                                       {true, AppDir} = get_deps_dir(
+                                                          Config1, Dep#dep.app),
+                                       collect_deps(AppDir, C)
+                                   end || Dep <- Deps]);
+        _ ->
+            []
+    end.
+
+
+%% ===================================================================
+%% Source helper functions
+%% ===================================================================
+
+source_engine_avail(Source) ->
+    Name = element(1, Source),
+    source_engine_avail(Name, Source).
+
+source_engine_avail(Name, Source)
+  when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync;
+       Name == fossil ->
+    case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of
+        true ->
+            true;
+        false ->
+            ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n",
+                   [required_vcs_client_vsn(Name), Name, Source])
+    end.
+
+vcs_client_vsn(false, _VsnArg, _VsnRegex) ->
+    false;
+vcs_client_vsn(Path, VsnArg, VsnRegex) ->
+    {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]},
+                                                 {use_stdout, false}]),
+    case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of
+        {match, Match} ->
+            list_to_tuple([list_to_integer(S) || S <- Match]);
+        _ ->
+            false
+    end.
+
+required_vcs_client_vsn(hg)     -> {1, 1};
+required_vcs_client_vsn(git)    -> {1, 5};
+required_vcs_client_vsn(bzr)    -> {2, 0};
+required_vcs_client_vsn(svn)    -> {1, 6};
+required_vcs_client_vsn(rsync)  -> {2, 0};
+required_vcs_client_vsn(fossil) -> {1, 0}.
+
+vcs_client_vsn(hg) ->
+    vcs_client_vsn(rebar_utils:find_executable("hg"), " --version",
+                   "version (\\d+).(\\d+)");
+vcs_client_vsn(git) ->
+    vcs_client_vsn(rebar_utils:find_executable("git"), " --version",
+                   "git version (\\d+).(\\d+)");
+vcs_client_vsn(bzr) ->
+    vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version",
+                   "Bazaar \\(bzr\\) (\\d+).(\\d+)");
+vcs_client_vsn(svn) ->
+    vcs_client_vsn(rebar_utils:find_executable("svn"), " --version",
+                   "svn, version (\\d+).(\\d+)");
+vcs_client_vsn(rsync) ->
+    vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version",
+                   "rsync  version (\\d+).(\\d+)");
+vcs_client_vsn(fossil) ->
+    vcs_client_vsn(rebar_utils:find_executable("fossil"), " version",
+                   "version (\\d+).(\\d+)").
+
+has_vcs_dir(git, Dir) ->
+    filelib:is_dir(filename:join(Dir, ".git"));
+has_vcs_dir(hg, Dir) ->
+    filelib:is_dir(filename:join(Dir, ".hg"));
+has_vcs_dir(bzr, Dir) ->
+    filelib:is_dir(filename:join(Dir, ".bzr"));
+has_vcs_dir(svn, Dir) ->
+    filelib:is_dir(filename:join(Dir, ".svn"))
+        orelse filelib:is_dir(filename:join(Dir, "_svn"));
+has_vcs_dir(rsync, _) ->
+    true;
+has_vcs_dir(_, _) ->
+    true.
+
+print_source(#dep{app=App, source=Source}) ->
+    ?CONSOLE("~s~n", [format_source(App, Source)]).
+
+format_source(App, {git, Url}) ->
+    ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
+format_source(App, {git, Url, ""}) ->
+    ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
+format_source(App, {git, Url, {branch, Branch}}) ->
+    ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]);
+format_source(App, {git, Url, {tag, Tag}}) ->
+    ?FMT("~p TAG ~s ~s", [App, Tag, Url]);
+format_source(App, {_, Url, Rev}) ->
+    ?FMT("~p REV ~s ~s", [App, Rev, Url]);
+format_source(App, undefined) ->
+    ?FMT("~p", [App]).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_dia_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_dia_compiler.erl b/src/rebar/src/rebar_dia_compiler.erl
new file mode 100644
index 0000000..f81c734
--- /dev/null
+++ b/src/rebar/src/rebar_dia_compiler.erl
@@ -0,0 +1,106 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_dia_compiler).
+
+-export([compile/2, clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+-spec compile(rebar_config:config(), file:filename()) -> 'ok'.
+compile(Config, _AppFile) ->
+    rebar_base_compiler:run(Config, filelib:wildcard("dia/*.dia"),
+                            "dia", ".dia", "src", ".erl",
+                            fun compile_dia/3).
+
+-spec clean(rebar_config:config(), file:filename()) -> 'ok'.
+clean(_Config, _AppFile) ->
+    GeneratedFiles = dia_generated_files("dia", "src", "include"),
+    ok = rebar_file_utils:delete_each(GeneratedFiles),
+    ok.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+    info_help("Build Diameter (*.dia) sources");
+info(help, clean) ->
+    info_help("Delete generated Diameter files").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  {dia_opts, []} (see diameter_codegen:from_dict/4 documentation)~n",
+       [Description]).
+
+-spec compile_dia(file:filename(), file:filename(),
+                   rebar_config:config()) -> ok.
+compile_dia(Source, Target, Config) ->
+    ok = filelib:ensure_dir(Target),
+    ok = filelib:ensure_dir(filename:join("include", "dummy.hrl")),
+    Opts = [{outdir, "src"}] ++ rebar_config:get(Config, dia_opts, []),
+    case diameter_dict_util:parse({path, Source}, []) of
+        {ok, Spec} ->
+            FileName = dia_filename(Source, Spec),
+            diameter_codegen:from_dict(FileName, Spec, Opts, erl),
+            diameter_codegen:from_dict(FileName, Spec, Opts, hrl),
+            HrlFile = filename:join("src", FileName ++ ".hrl"),
+            case filelib:is_regular(HrlFile) of
+                true ->
+                    ok = rebar_file_utils:mv(HrlFile, "include");
+                false ->
+                    ok
+            end;
+        {error, Reason} ->
+            ?ERROR("~s~n", [diameter_dict_util:format_error(Reason)])
+    end.
+
+dia_generated_files(DiaDir, SrcDir, IncDir) ->
+    F = fun(File, Acc) ->
+            {ok, Spec} = diameter_dict_util:parse({path, File}, []),
+            FileName = dia_filename(File, Spec),
+            [filename:join([IncDir, FileName ++ ".hrl"]) |
+             filelib:wildcard(filename:join([SrcDir, FileName ++ ".*"]))] ++ Acc
+    end,
+    lists:foldl(F, [], filelib:wildcard(filename:join([DiaDir, "*.dia"]))).
+
+dia_filename(File, Spec) ->
+    case proplists:get_value(name, Spec) of
+        undefined ->
+            filename:rootname(filename:basename(File));
+        Name ->
+            Name
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_edoc.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_edoc.erl b/src/rebar/src/rebar_edoc.erl
new file mode 100644
index 0000000..c828d27
--- /dev/null
+++ b/src/rebar/src/rebar_edoc.erl
@@ -0,0 +1,130 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2010 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+%% @author Dave Smith <di...@dizzyd.com>
+%% @doc rebar_edoc supports the following command:
+%% <ul>
+%%   <li>doc (essentially erl -noshell -run edoc_run application
+%% "'$(&lt;app_name&gt;)'"
+%% '"."' '[&lt;options&gt;]')</li>
+%% </ul>
+%% EDoc options can be given in the <code>edoc_opts</code> option in
+%% <code>rebar.config</code>.
+%% @copyright 2010 Dave Smith
+%% -------------------------------------------------------------------
+-module(rebar_edoc).
+
+-export([doc/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+doc(Config, File) ->
+    %% Save code path
+    CodePath = setup_code_path(),
+
+    %% Get the edoc_opts and app file info
+    EDocOpts = rebar_config:get(Config, edoc_opts, []),
+    {ok, Config1, AppName, _AppData} =
+        rebar_app_utils:load_app_file(Config, File),
+
+    case needs_regen(EDocOpts) of
+        true ->
+            ?INFO("Regenerating edocs for ~p\n", [AppName]),
+            ok = edoc:application(AppName, ".", EDocOpts);
+        false ->
+            ?INFO("Skipping regeneration of edocs for ~p\n", [AppName]),
+            ok
+    end,
+
+    %% Restore code path
+    true = code:set_path(CodePath),
+    {ok, Config1}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, doc) ->
+    ?CONSOLE(
+       "Generate Erlang program documentation.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  {edoc_opts, []} (see edoc:application/3 documentation)~n",
+       []).
+
+setup_code_path() ->
+    %% Setup code path prior to calling edoc so that edown, asciiedoc,
+    %% and the like can work properly when generating their own
+    %% documentation.
+    CodePath = code:get_path(),
+    true = code:add_patha(rebar_utils:ebin_dir()),
+    CodePath.
+
+-type path_spec() :: {'file', file:filename()} | file:filename().
+-spec newer_file_exists(Paths::[path_spec()], OldFile::string()) -> boolean().
+newer_file_exists(Paths, OldFile) ->
+    OldModTime = filelib:last_modified(OldFile),
+
+    ThrowIfNewer = fun(Fn, _Acc) ->
+                           FModTime = filelib:last_modified(Fn),
+                           (FModTime > OldModTime) andalso
+                               throw({newer_file_exists, {Fn, FModTime}})
+                   end,
+
+    try
+        lists:foldl(fun({file, F}, _) ->
+                            ThrowIfNewer(F, false);
+                       (P, _) ->
+                            filelib:fold_files(P, ".*.erl", true,
+                                               ThrowIfNewer, false)
+                    end, undefined, Paths)
+    catch
+        throw:{newer_file_exists, {Filename, FMod}} ->
+            ?DEBUG("~p is more recent than ~p: "
+                   "~120p > ~120p\n",
+                   [Filename, OldFile, FMod, OldModTime]),
+            true
+    end.
+
+%% Needs regen if any dependent file is changed since the last
+%% edoc run. Dependent files are the erlang source files,
+%% and the overview file, if it exists.
+-spec needs_regen(proplists:proplist()) -> boolean().
+needs_regen(EDocOpts) ->
+    DocDir = proplists:get_value(dir, EDocOpts, "doc"),
+    EDocInfoName = filename:join(DocDir, "edoc-info"),
+    OverviewFile = proplists:get_value(overview, EDocOpts, "overview.edoc"),
+    EDocOverviewName = filename:join(DocDir, OverviewFile),
+    SrcPaths = proplists:get_value(source_path, EDocOpts, ["src"]),
+
+    newer_file_exists([{file, EDocOverviewName} | SrcPaths], EDocInfoName).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_erlc_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_erlc_compiler.erl b/src/rebar/src/rebar_erlc_compiler.erl
new file mode 100644
index 0000000..dbefa4a
--- /dev/null
+++ b/src/rebar/src/rebar_erlc_compiler.erl
@@ -0,0 +1,511 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_erlc_compiler).
+
+-export([compile/2,
+         clean/2]).
+
+%% for internal use only
+-export([test_compile/3,
+         info/2]).
+
+-include("rebar.hrl").
+-include_lib("stdlib/include/erl_compile.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+%% Supported configuration variables:
+%%
+%% * erl_opts - Erlang list of options passed to compile:file/2
+%%              It is also possible to specify platform specific
+%%              options by specifying a pair or a triplet where the
+%%              first string is a regex that is checked against the
+%%              string
+%%
+%%                OtpRelease ++ "-" ++ SysArch ++ "-" ++ Words.
+%%
+%%              where
+%%
+%%                OtpRelease = erlang:system_info(otp_release).
+%%                SysArch = erlang:system_info(system_architecture).
+%%                Words = integer_to_list(8 *
+%%                            erlang:system_info({wordsize, external})).
+%%
+%%              E.g. to define HAVE_SENDFILE only on systems with
+%%              sendfile(), to define BACKLOG on Linux/FreeBSD as 128,
+%%              and to define 'old_inets' for R13 OTP release do:
+%%
+%%              {erl_opts, [{platform_define,
+%%                           "(linux|solaris|freebsd|darwin)",
+%%                           'HAVE_SENDFILE'},
+%%                          {platform_define, "(linux|freebsd)",
+%%                           'BACKLOG', 128},
+%%                          {platform_define, "R13",
+%%                           'old_inets'}]}.
+%%
+
+-spec compile(rebar_config:config(), file:filename()) -> 'ok'.
+compile(Config, _AppFile) ->
+    rebar_base_compiler:run(Config,
+                            check_files(rebar_config:get_local(
+                                          Config, xrl_first_files, [])),
+                            "src", ".xrl", "src", ".erl",
+                            fun compile_xrl/3),
+    rebar_base_compiler:run(Config,
+                            check_files(rebar_config:get_local(
+                                          Config, yrl_first_files, [])),
+                            "src", ".yrl", "src", ".erl",
+                            fun compile_yrl/3),
+    rebar_base_compiler:run(Config,
+                            check_files(rebar_config:get_local(
+                                          Config, mib_first_files, [])),
+                            "mibs", ".mib", "priv/mibs", ".bin",
+                            fun compile_mib/3),
+    doterl_compile(Config, "ebin").
+
+-spec clean(rebar_config:config(), file:filename()) -> 'ok'.
+clean(_Config, _AppFile) ->
+    MibFiles = rebar_utils:find_files("mibs", "^.*\\.mib\$"),
+    MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
+    rebar_file_utils:delete_each(
+      [filename:join(["include",MIB++".hrl"]) || MIB <- MIBs]),
+    lists:foreach(fun(F) -> ok = rebar_file_utils:rm_rf(F) end,
+                  ["ebin/*.beam", "priv/mibs/*.bin"]),
+
+    YrlFiles = rebar_utils:find_files("src", "^.*\\.[x|y]rl\$"),
+    rebar_file_utils:delete_each(
+      [ binary_to_list(iolist_to_binary(re:replace(F, "\\.[x|y]rl$", ".erl")))
+        || F <- YrlFiles ]),
+
+    %% Erlang compilation is recursive, so it's possible that we have a nested
+    %% directory structure in ebin with .beam files within. As such, we want
+    %% to scan whatever is left in the ebin/ directory for sub-dirs which
+    %% satisfy our criteria.
+    BeamFiles = rebar_utils:find_files("ebin", "^.*\\.beam\$"),
+    rebar_file_utils:delete_each(BeamFiles),
+    lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")),
+    ok.
+
+%% ===================================================================
+%% .erl Compilation API (externally used by only eunit and qc)
+%% ===================================================================
+
+test_compile(Config, Cmd, OutDir) ->
+    %% Obtain all the test modules for inclusion in the compile stage.
+    TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
+
+    %% Copy source files to eunit dir for cover in case they are not directly
+    %% in src but in a subdirectory of src. Cover only looks in cwd and ../src
+    %% for source files. Also copy files from src_dirs.
+    ErlOpts = rebar_utils:erl_opts(Config),
+
+    SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
+    SrcErls = lists:foldl(
+                fun(Dir, Acc) ->
+                        Files = rebar_utils:find_files(Dir, ".*\\.erl\$"),
+                        lists:append(Acc, Files)
+                end, [], SrcDirs),
+
+    %% If it is not the first time rebar eunit is executed, there will be source
+    %% files already present in OutDir. Since some SCMs (like Perforce) set
+    %% the source files as being read only (unless they are checked out), we
+    %% need to be sure that the files already present in OutDir are writable
+    %% before doing the copy. This is done here by removing any file that was
+    %% already present before calling rebar_file_utils:cp_r.
+
+    %% Get the full path to a file that was previously copied in OutDir
+    ToCleanUp = fun(F, Acc) ->
+                        F2 = filename:basename(F),
+                        F3 = filename:join([OutDir, F2]),
+                        case filelib:is_regular(F3) of
+                            true -> [F3|Acc];
+                            false -> Acc
+                        end
+                end,
+
+    ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)),
+    ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)),
+
+    ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, OutDir),
+
+    %% Compile erlang code to OutDir, using a tweaked config
+    %% with appropriate defines for eunit, and include all the test modules
+    %% as well.
+    ok = doterl_compile(test_compile_config(Config, ErlOpts, Cmd),
+                        OutDir, TestErls),
+
+    {ok, SrcErls}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+    info_help("Build *.erl, *.yrl, *.xrl, and *.mib sources");
+info(help, clean) ->
+    info_help("Delete *.erl, *.yrl, *.xrl, and *.mib build results").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n",
+       [
+        Description,
+        {erl_opts, [no_debug_info,
+                    {i, "myinclude"},
+                    {src_dirs, ["src", "src2", "src3"]},
+                    {platform_define,
+                     "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'},
+                    {platform_define, "(linux|freebsd)", 'BACKLOG', 128},
+                    {platform_define, "R13", 'old_inets'}]},
+        {erl_first_files, ["mymib1", "mymib2"]},
+        {mib_opts, []},
+        {mib_first_files, []},
+        {xrl_opts, []},
+        {xrl_first_files, []},
+        {yrl_opts, []},
+        {yrl_first_files, []}
+       ]).
+
+test_compile_config(Config, ErlOpts, Cmd) ->
+    {Config1, TriqOpts} = triq_opts(Config),
+    {Config2, PropErOpts} = proper_opts(Config1),
+    {Config3, EqcOpts} = eqc_opts(Config2),
+
+    OptsAtom = list_to_atom(Cmd ++ "_compile_opts"),
+    EunitOpts = rebar_config:get_list(Config3, OptsAtom, []),
+    Opts0 = [{d, 'TEST'}] ++
+        ErlOpts ++ EunitOpts ++ TriqOpts ++ PropErOpts ++ EqcOpts,
+    Opts = [O || O <- Opts0, O =/= no_debug_info],
+    Config4 = rebar_config:set(Config3, erl_opts, Opts),
+
+    FirstFilesAtom = list_to_atom(Cmd ++ "_first_files"),
+    FirstErls = rebar_config:get_list(Config4, FirstFilesAtom, []),
+    rebar_config:set(Config4, erl_first_files, FirstErls).
+
+triq_opts(Config) ->
+    {NewConfig, IsAvail} = is_lib_avail(Config, is_triq_avail, triq,
+                                        "triq.hrl", "Triq"),
+    Opts = define_if('TRIQ', IsAvail),
+    {NewConfig, Opts}.
+
+proper_opts(Config) ->
+    {NewConfig, IsAvail} = is_lib_avail(Config, is_proper_avail, proper,
+                                        "proper.hrl", "PropEr"),
+    Opts = define_if('PROPER', IsAvail),
+    {NewConfig, Opts}.
+
+eqc_opts(Config) ->
+    {NewConfig, IsAvail} = is_lib_avail(Config, is_eqc_avail, eqc,
+                                        "eqc.hrl", "QuickCheck"),
+    Opts = define_if('EQC', IsAvail),
+    {NewConfig, Opts}.
+
+define_if(Def, true) -> [{d, Def}];
+define_if(_Def, false) -> [].
+
+is_lib_avail(Config, DictKey, Mod, Hrl, Name) ->
+    case rebar_config:get_xconf(Config, DictKey, undefined) of
+        undefined ->
+            IsAvail = case code:lib_dir(Mod, include) of
+                          {error, bad_name} ->
+                              false;
+                          Dir ->
+                              filelib:is_regular(filename:join(Dir, Hrl))
+                      end,
+            NewConfig = rebar_config:set_xconf(Config, DictKey, IsAvail),
+            ?DEBUG("~s availability: ~p\n", [Name, IsAvail]),
+            {NewConfig, IsAvail};
+        IsAvail ->
+            {Config, IsAvail}
+    end.
+
+-spec doterl_compile(rebar_config:config(), file:filename()) -> 'ok'.
+doterl_compile(Config, OutDir) ->
+    doterl_compile(Config, OutDir, []).
+
+doterl_compile(Config, OutDir, MoreSources) ->
+    FirstErls = rebar_config:get_list(Config, erl_first_files, []),
+    ErlOpts = rebar_utils:erl_opts(Config),
+    ?DEBUG("erl_opts ~p~n", [ErlOpts]),
+    %% Support the src_dirs option allowing multiple directories to
+    %% contain erlang source. This might be used, for example, should
+    %% eunit tests be separated from the core application source.
+    SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
+    RestErls  = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources,
+                           not lists:member(Source, FirstErls)],
+
+    %% Split RestErls so that parse_transforms and behaviours are instead added
+    %% to erl_first_files, parse transforms first.
+    %% This should probably be somewhat combined with inspect_epp
+    [ParseTransforms, Behaviours, OtherErls] =
+        lists:foldl(fun(F, [A, B, C]) ->
+                            case compile_priority(F) of
+                                parse_transform ->
+                                    [[F | A], B, C];
+                                behaviour ->
+                                    [A, [F | B], C];
+                                callback ->
+                                    [A, [F | B], C];
+                                _ ->
+                                    [A, B, [F | C]]
+                            end
+                    end, [[], [], []], RestErls),
+
+    NewFirstErls = FirstErls ++ ParseTransforms ++ Behaviours,
+
+    %% Make sure that ebin/ exists and is on the path
+    ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")),
+    CurrPath = code:get_path(),
+    true = code:add_path(filename:absname("ebin")),
+    OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir),
+    rebar_base_compiler:run(Config, NewFirstErls, OtherErls,
+                            fun(S, C) ->
+                                    internal_erl_compile(C, S, OutDir1, ErlOpts)
+                            end),
+    true = code:set_path(CurrPath),
+    ok.
+
+-spec include_path(file:filename(),
+                   rebar_config:config()) -> [file:filename(), ...].
+include_path(Source, Config) ->
+    ErlOpts = rebar_config:get(Config, erl_opts, []),
+    ["include", filename:dirname(Source)]
+        ++ proplists:get_all_values(i, ErlOpts).
+
+-spec inspect(file:filename(),
+              [file:filename(), ...]) -> {string(), [string()]}.
+inspect(Source, IncludePath) ->
+    ModuleDefault = filename:basename(Source, ".erl"),
+    case epp:open(Source, IncludePath) of
+        {ok, Epp} ->
+            inspect_epp(Epp, Source, ModuleDefault, []);
+        {error, Reason} ->
+            ?DEBUG("Failed to inspect ~s: ~p\n", [Source, Reason]),
+            {ModuleDefault, []}
+    end.
+
+-spec inspect_epp(pid(), file:filename(), file:filename(),
+                  [string()]) -> {string(), [string()]}.
+inspect_epp(Epp, Source, Module, Includes) ->
+    case epp:parse_erl_form(Epp) of
+        {ok, {attribute, _, module, ModInfo}} ->
+            ActualModuleStr =
+                case ModInfo of
+                    %% Typical module name, single atom
+                    ActualModule when is_atom(ActualModule) ->
+                        atom_to_list(ActualModule);
+                    %% Packag-ized module name, list of atoms
+                    ActualModule when is_list(ActualModule) ->
+                        string:join([atom_to_list(P) ||
+                                        P <- ActualModule], ".");
+                    %% Parameterized module name, single atom
+                    {ActualModule, _} when is_atom(ActualModule) ->
+                        atom_to_list(ActualModule);
+                    %% Parameterized and packagized module name, list of atoms
+                    {ActualModule, _} when is_list(ActualModule) ->
+                        string:join([atom_to_list(P) ||
+                                        P <- ActualModule], ".")
+                end,
+            inspect_epp(Epp, Source, ActualModuleStr, Includes);
+        {ok, {attribute, 1, file, {Module, 1}}} ->
+            inspect_epp(Epp, Source, Module, Includes);
+        {ok, {attribute, 1, file, {Source, 1}}} ->
+            inspect_epp(Epp, Source, Module, Includes);
+        {ok, {attribute, 1, file, {IncFile, 1}}} ->
+            inspect_epp(Epp, Source, Module, [IncFile | Includes]);
+        {eof, _} ->
+            epp:close(Epp),
+            {Module, Includes};
+        _ ->
+            inspect_epp(Epp, Source, Module, Includes)
+    end.
+
+-spec needs_compile(file:filename(), file:filename(),
+                    [string()]) -> boolean().
+needs_compile(Source, Target, Hrls) ->
+    TargetLastMod = filelib:last_modified(Target),
+    lists:any(fun(I) -> TargetLastMod < filelib:last_modified(I) end,
+              [Source] ++ Hrls).
+
+-spec internal_erl_compile(rebar_config:config(), file:filename(),
+                           file:filename(), list()) -> 'ok' | 'skipped'.
+internal_erl_compile(Config, Source, Outdir, ErlOpts) ->
+    %% Determine the target name and includes list by inspecting the source file
+    {Module, Hrls} = inspect(Source, include_path(Source, Config)),
+
+    %% Construct the target filename
+    Target = filename:join([Outdir | string:tokens(Module, ".")]) ++ ".beam",
+    ok = filelib:ensure_dir(Target),
+
+    %% If the file needs compilation, based on last mod date of includes or
+    %% the target
+    case needs_compile(Source, Target, Hrls) of
+        true ->
+            Opts = [{outdir, filename:dirname(Target)}] ++
+                ErlOpts ++ [{i, "include"}, return],
+            case compile:file(Source, Opts) of
+                {ok, _Mod} ->
+                    ok;
+                {ok, _Mod, Ws} ->
+                    rebar_base_compiler:ok_tuple(Config, Source, Ws);
+                {error, Es, Ws} ->
+                    rebar_base_compiler:error_tuple(Config, Source,
+                                                    Es, Ws, Opts)
+            end;
+        false ->
+            skipped
+    end.
+
+-spec compile_mib(file:filename(), file:filename(),
+                  rebar_config:config()) -> 'ok'.
+compile_mib(Source, Target, Config) ->
+    ok = rebar_utils:ensure_dir(Target),
+    ok = rebar_utils:ensure_dir(filename:join("include", "dummy.hrl")),
+    Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++
+        rebar_config:get(Config, mib_opts, []),
+    case snmpc:compile(Source, Opts) of
+        {ok, _} ->
+            Mib = filename:rootname(Target),
+            MibToHrlOpts =
+                case proplists:get_value(verbosity, Opts, undefined) of
+                    undefined ->
+                        #options{specific = []};
+                    Verbosity ->
+                        #options{specific = [{verbosity, Verbosity}]}
+                end,
+            ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
+            Hrl_filename = Mib ++ ".hrl",
+            rebar_file_utils:mv(Hrl_filename, "include"),
+            ok;
+        {error, compilation_failed} ->
+            ?FAIL
+    end.
+
+-spec compile_xrl(file:filename(), file:filename(),
+                  rebar_config:config()) -> 'ok'.
+compile_xrl(Source, Target, Config) ->
+    Opts = [{scannerfile, Target} | rebar_config:get(Config, xrl_opts, [])],
+    compile_xrl_yrl(Config, Source, Target, Opts, leex).
+
+-spec compile_yrl(file:filename(), file:filename(),
+                  rebar_config:config()) -> 'ok'.
+compile_yrl(Source, Target, Config) ->
+    Opts = [{parserfile, Target} | rebar_config:get(Config, yrl_opts, [])],
+    compile_xrl_yrl(Config, Source, Target, Opts, yecc).
+
+-spec compile_xrl_yrl(rebar_config:config(), file:filename(),
+                      file:filename(), list(), module()) -> 'ok'.
+compile_xrl_yrl(Config, Source, Target, Opts, Mod) ->
+    case needs_compile(Source, Target, []) of
+        true ->
+            case Mod:file(Source, Opts ++ [{return, true}]) of
+                {ok, _} ->
+                    ok;
+                {ok, _Mod, Ws} ->
+                    rebar_base_compiler:ok_tuple(Config, Source, Ws);
+                {error, Es, Ws} ->
+                    rebar_base_compiler:error_tuple(Config, Source,
+                                                    Es, Ws, Opts)
+            end;
+        false ->
+            skipped
+    end.
+
+gather_src([], Srcs) ->
+    Srcs;
+gather_src([Dir|Rest], Srcs) ->
+    gather_src(Rest, Srcs ++ rebar_utils:find_files(Dir, ".*\\.erl\$")).
+
+-spec dirs(file:filename()) -> [file:filename()].
+dirs(Dir) ->
+    [F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)].
+
+-spec delete_dir(file:filename(), [string()]) -> 'ok' | {'error', atom()}.
+delete_dir(Dir, []) ->
+    file:del_dir(Dir);
+delete_dir(Dir, Subdirs) ->
+    lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
+    file:del_dir(Dir).
+
+-spec compile_priority(file:filename()) -> 'normal' | 'behaviour' |
+                                           'callback' |
+                                           'parse_transform'.
+compile_priority(File) ->
+    case epp_dodger:parse_file(File) of
+        {error, _} ->
+            normal; % couldn't parse the file, default priority
+        {ok, Trees} ->
+            F2 = fun({tree,arity_qualifier,_,
+                      {arity_qualifier,{tree,atom,_,behaviour_info},
+                       {tree,integer,_,1}}}, _) ->
+                         behaviour;
+                    ({tree,arity_qualifier,_,
+                      {arity_qualifier,{tree,atom,_,parse_transform},
+                       {tree,integer,_,2}}}, _) ->
+                         parse_transform;
+                    (_, Acc) ->
+                         Acc
+                 end,
+
+            F = fun({tree, attribute, _,
+                     {attribute, {tree, atom, _, export},
+                      [{tree, list, _, {list, List, none}}]}}, Acc) ->
+                        lists:foldl(F2, Acc, List);
+                   ({tree, attribute, _,
+                     {attribute, {tree, atom, _, callback},_}}, _Acc) ->
+                        callback;
+                   (_, Acc) ->
+                        Acc
+                end,
+
+            lists:foldl(F, normal, Trees)
+    end.
+
+%%
+%% Ensure all files in a list are present and abort if one is missing
+%%
+-spec check_files([file:filename()]) -> [file:filename()].
+check_files(FileList) ->
+    [check_file(F) || F <- FileList].
+
+check_file(File) ->
+    case filelib:is_regular(File) of
+        false -> ?ABORT("File ~p is missing, aborting\n", [File]);
+        true -> File
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_erlydtl_compiler.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_erlydtl_compiler.erl b/src/rebar/src/rebar_erlydtl_compiler.erl
new file mode 100644
index 0000000..6172879
--- /dev/null
+++ b/src/rebar/src/rebar_erlydtl_compiler.erl
@@ -0,0 +1,265 @@
+%% -*- 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),
+%%                    Bryan Fink (bryan@basho.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+
+%% The rebar_erlydtl_compiler module is a plugin for rebar that compiles
+%% ErlyDTL templates.  By default, it compiles all templates/*.dtl
+%% to ebin/*_dtl.beam.
+%%
+%% Configuration options should be placed in rebar.config under
+%% 'erlydtl_opts'.  It can be a list of name-value tuples or a list of
+%% lists of name-value tuples if you have multiple template directories
+%% that need to have different settings (see example below).
+%%
+%% Available options include:
+%%
+%%  doc_root: where to find templates to compile
+%%            "templates" by default
+%%
+%%  out_dir: where to put compiled template beam files
+%%           "ebin" by default
+%%
+%%  source_ext: the file extension the template sources have
+%%              ".dtl" by default
+%%
+%%  module_ext: characters to append to the template's module name
+%%              "_dtl" by default
+%%
+%%  recursive: boolean that determines if doc_root(s) need to be
+%%             scanned recursively for matching template file names
+%%             (default: true).
+%% For example, if you had:
+%%   /t_src/
+%%          base.html
+%%          foo.html
+%%
+%% And you wanted them compiled to:
+%%   /priv/
+%%         base.beam
+%%         foo.beam
+%%
+%% You would add to your rebar.config:
+%%   {erlydtl_opts, [
+%%               {doc_root,   "t_src"},
+%%               {out_dir,    "priv"},
+%%               {source_ext, ".html"},
+%%               {module_ext, ""}
+%%              ]}.
+%%
+%% The default settings are the equivalent of:
+%%   {erlydtl_opts, [
+%%               {doc_root,   "templates"},
+%%               {out_dir,    "ebin"},
+%%               {source_ext, ".dtl"},
+%%               {module_ext, "_dtl"}
+%%              ]}.
+%%
+%% The following example will compile the following templates:
+%% "src/*.dtl" files into "ebin/*_dtl.beam" and
+%% "templates/*.html" into "ebin/*.beam". Note that any tuple option
+%% (such as 'out_dir') in the outer list is added to each inner list:
+%%   {erlydtl_opts, [
+%%      {out_dir, "ebin"},
+%%      {recursive, false},
+%%      [
+%%          {doc_root, "src"}, {module_ext, "_dtl"}
+%%      ],
+%%      [
+%%          {doc_root, "templates", {module_ext, ""}, {source_ext, ".html"}
+%%      ]
+%%   ]}.
+-module(rebar_erlydtl_compiler).
+
+-export([compile/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+compile(Config, _AppFile) ->
+    MultiDtlOpts = erlydtl_opts(Config),
+    OrigPath = code:get_path(),
+    true = code:add_path(rebar_utils:ebin_dir()),
+
+    Result = lists:foldl(fun(DtlOpts, _) ->
+            rebar_base_compiler:run(Config, [],
+                                     option(doc_root, DtlOpts),
+                                     option(source_ext, DtlOpts),
+                                     option(out_dir, DtlOpts),
+                                     option(module_ext, DtlOpts) ++ ".beam",
+                                     fun(S, T, C) ->
+                                        compile_dtl(C, S, T, DtlOpts)
+                                     end,
+                                     [{check_last_mod, false},
+                                      {recursive, option(recursive, DtlOpts)}])
+        end, ok, MultiDtlOpts),
+
+    true = code:set_path(OrigPath),
+    Result.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+    ?CONSOLE(
+       "Build ErlyDtl (*.dtl) sources.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~p~n",
+       [
+        {erlydtl_opts, [{doc_root,   "templates"},
+                        {out_dir,    "ebin"},
+                        {source_ext, ".dtl"},
+                        {module_ext, "_dtl"},
+                        {recursive, true}]}
+       ]).
+
+erlydtl_opts(Config) ->
+    Opts = rebar_config:get(Config, erlydtl_opts, []),
+    Tuples = [{K,V} || {K,V} <- Opts],
+    case [L || L <- Opts, is_list(L), not io_lib:printable_list(L)] of
+        [] ->
+            [lists:keysort(1, Tuples)];
+        Lists ->
+            lists:map(
+              fun(L) ->
+                      lists:keysort(1,
+                                    lists:foldl(
+                                      fun({K,T}, Acc) ->
+                                              lists:keystore(K, 1, Acc, {K, T})
+                                      end, Tuples, L))
+              end, Lists)
+    end.
+
+option(Opt, DtlOpts) ->
+    proplists:get_value(Opt, DtlOpts, default(Opt)).
+
+default(doc_root) -> "templates";
+default(out_dir)  -> "ebin";
+default(source_ext) -> ".dtl";
+default(module_ext) -> "_dtl";
+default(custom_tags_dir) -> "";
+default(compiler_options) -> [return];
+default(recursive) -> true.
+
+compile_dtl(Config, Source, Target, DtlOpts) ->
+    case code:which(erlydtl) of
+        non_existing ->
+            ?ERROR("~n===============================================~n"
+                   " You need to install erlydtl to compile DTL templates~n"
+                   " Download the latest tarball release from github~n"
+                   "    http://code.google.com/p/erlydtl/~n"
+                   " and install it into your erlang library dir~n"
+                   "===============================================~n~n", []),
+            ?FAIL;
+        _ ->
+            case needs_compile(Source, Target, DtlOpts) of
+                true ->
+                    do_compile(Config, Source, Target, DtlOpts);
+                false ->
+                    skipped
+            end
+    end.
+
+do_compile(Config, Source, Target, DtlOpts) ->
+    %% TODO: Check last mod on target and referenced DTLs here..
+
+    %% ensure that doc_root and out_dir are defined,
+    %% using defaults if necessary
+    Opts = lists:ukeymerge(1,
+            DtlOpts,
+            lists:sort(
+                [{out_dir, option(out_dir, DtlOpts)},
+                 {doc_root, option(doc_root, DtlOpts)},
+                 {custom_tags_dir, option(custom_tags_dir, DtlOpts)},
+                 {compiler_options, option(compiler_options, DtlOpts)}])),
+    ?INFO("Compiling \"~s\" -> \"~s\" with options:~n    ~s~n",
+        [Source, Target, io_lib:format("~p", [Opts])]),
+    case erlydtl:compile(Source,
+                         module_name(Target),
+                         Opts) of
+        ok ->
+            ok;
+        error ->
+            rebar_base_compiler:error_tuple(Config, Source, [], [], Opts);
+        {error, {_File, _Msgs} = Error} ->
+            rebar_base_compiler:error_tuple(Config, Source, [Error], [], Opts);
+        {error, Msg} ->
+            Es = [{Source, [{erlydtl_parser, Msg}]}],
+            rebar_base_compiler:error_tuple(Config, Source, Es, [], Opts)
+    end.
+
+module_name(Target) ->
+    F = filename:basename(Target),
+    string:substr(F, 1, length(F)-length(".beam")).
+
+needs_compile(Source, Target, DtlOpts) ->
+    LM = filelib:last_modified(Target),
+    LM < filelib:last_modified(Source) orelse
+        lists:any(fun(D) -> LM < filelib:last_modified(D) end,
+                  referenced_dtls(Source, DtlOpts)).
+
+referenced_dtls(Source, DtlOpts) ->
+    DtlOpts1 = lists:keyreplace(doc_root, 1, DtlOpts,
+        {doc_root, filename:dirname(Source)}),
+    Set = referenced_dtls1([Source], DtlOpts1,
+                           sets:add_element(Source, sets:new())),
+    sets:to_list(sets:del_element(Source, Set)).
+
+referenced_dtls1(Step, DtlOpts, Seen) ->
+    ExtMatch = re:replace(option(source_ext, DtlOpts), "\.", "\\\\\\\\.",
+                          [{return, list}]),
+
+    ShOpts = [{use_stdout, false}, return_on_error],
+    AllRefs =
+        lists:append(
+          [begin
+               Cmd = lists:flatten(["grep -o [^\\\"]*\\",
+                                    ExtMatch, "[^\\\"]* ", F]),
+               case rebar_utils:sh(Cmd, ShOpts) of
+                   {ok, Res} ->
+                       string:tokens(Res, "\n");
+                   {error, _} ->
+                       ""
+               end
+           end || F <- Step]),
+    DocRoot = option(doc_root, DtlOpts),
+    WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ],
+    ?DEBUG("All deps: ~p\n", [WithPaths]),
+    Existing = [F || F <- WithPaths, filelib:is_regular(F)],
+    New = sets:subtract(sets:from_list(Existing), Seen),
+    case sets:size(New) of
+        0 -> Seen;
+        _ -> referenced_dtls1(sets:to_list(New), DtlOpts,
+                              sets:union(New, Seen))
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/626455a4/src/rebar/src/rebar_escripter.erl
----------------------------------------------------------------------
diff --git a/src/rebar/src/rebar_escripter.erl b/src/rebar/src/rebar_escripter.erl
new file mode 100644
index 0000000..0cc43ef
--- /dev/null
+++ b/src/rebar/src/rebar_escripter.erl
@@ -0,0 +1,197 @@
+%% -*- 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_escripter).
+
+-export([escriptize/2,
+         clean/2]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+-include_lib("kernel/include/file.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+escriptize(Config0, AppFile) ->
+    %% Extract the application name from the archive -- this is the default
+    %% name of the generated script
+    {Config, AppName} = rebar_app_utils:app_name(Config0, AppFile),
+    AppNameStr = atom_to_list(AppName),
+
+    %% Get the output filename for the escript -- this may include dirs
+    Filename = rebar_config:get_local(Config, escript_name, AppName),
+    ok = filelib:ensure_dir(Filename),
+
+    %% Look for a list of other applications (dependencies) to include
+    %% in the output file. We then use the .app files for each of these
+    %% to pull in all the .beam files.
+    InclBeams = get_app_beams(
+                  rebar_config:get_local(Config, escript_incl_apps, []), []),
+
+    %% Look for a list of extra files to include in the output file.
+    %% For internal rebar-private use only. Do not use outside rebar.
+    InclExtra = get_extra(Config),
+
+    %% Construct the archive of everything in ebin/ dir -- put it on the
+    %% top-level of the zip file so that code loading works properly.
+    EbinPrefix = filename:join(AppNameStr, "ebin"),
+    EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")),
+    ExtraFiles = usort(InclBeams ++ InclExtra),
+    Files = EbinFiles ++ ExtraFiles,
+
+    case zip:create("mem", Files, [memory]) of
+        {ok, {"mem", ZipBin}} ->
+            %% Archive was successfully created. Prefix that binary with our
+            %% header and write to our escript file
+            Shebang = rebar_config:get(Config, escript_shebang,
+                                       "#!/usr/bin/env escript\n"),
+            Comment = rebar_config:get(Config, escript_comment, "%%\n"),
+            DefaultEmuArgs = ?FMT("%%! -pa ~s/~s/ebin\n",
+                                  [AppNameStr, AppNameStr]),
+            EmuArgs = rebar_config:get(Config, escript_emu_args,
+                                       DefaultEmuArgs),
+            Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]),
+            case file:write_file(Filename, Script) of
+                ok ->
+                    ok;
+                {error, WriteError} ->
+                    ?ERROR("Failed to write ~p script: ~p\n",
+                           [AppName, WriteError]),
+                    ?FAIL
+            end;
+        {error, ZipError} ->
+            ?ERROR("Failed to construct ~p escript: ~p\n",
+                   [AppName, ZipError]),
+            ?FAIL
+    end,
+
+    %% Finally, update executable perms for our script
+    {ok, #file_info{mode = Mode}} = file:read_file_info(Filename),
+    ok = file:change_mode(Filename, Mode bor 8#00111),
+    {ok, Config}.
+
+clean(Config0, AppFile) ->
+    %% Extract the application name from the archive -- this is the default
+    %% name of the generated script
+    {Config, AppName} = rebar_app_utils:app_name(Config0, AppFile),
+
+    %% Get the output filename for the escript -- this may include dirs
+    Filename = rebar_config:get_local(Config, escript_name, AppName),
+    rebar_file_utils:delete_each([Filename]),
+    {ok, Config}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, escriptize) ->
+    info_help("Generate escript archive");
+info(help, clean) ->
+    info_help("Delete generated escript archive").
+
+info_help(Description) ->
+    ?CONSOLE(
+       "~s.~n"
+       "~n"
+       "Valid rebar.config options:~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n"
+       "  ~p~n",
+       [
+        Description,
+        {escript_name, "application"},
+        {escript_incl_apps, []},
+        {escript_shebang, "#!/usr/bin/env escript\n"},
+        {escript_comment, "%%\n"},
+        {escript_emu_args, "%%! -pa application/application/ebin\n"}
+       ]).
+
+get_app_beams([], Acc) ->
+    Acc;
+get_app_beams([App | Rest], Acc) ->
+    case code:lib_dir(App, ebin) of
+        {error, bad_name} ->
+            ?ABORT("Failed to get ebin/ directory for "
+                   "~p escript_incl_apps.", [App]);
+        Path ->
+            Prefix = filename:join(atom_to_list(App), "ebin"),
+            Acc2 = load_files(Prefix, "*", Path),
+            get_app_beams(Rest, Acc2 ++ Acc)
+    end.
+
+get_extra(Config) ->
+    Extra = rebar_config:get_local(Config, escript_incl_extra, []),
+    lists:foldl(fun({Wildcard, Dir}, Files) ->
+                        load_files(Wildcard, Dir) ++ Files
+                end, [], Extra).
+
+load_files(Wildcard, Dir) ->
+    load_files("", Wildcard, Dir).
+
+load_files(Prefix, Wildcard, Dir) ->
+    [read_file(Prefix, Filename, Dir)
+     || Filename <- filelib:wildcard(Wildcard, Dir)].
+
+read_file(Prefix, Filename, Dir) ->
+    Filename1 = case Prefix of
+                    "" ->
+                        Filename;
+                    _ ->
+                        filename:join([Prefix, Filename])
+                end,
+    [dir_entries(filename:dirname(Filename1)),
+     {Filename1, file_contents(filename:join(Dir, Filename))}].
+
+file_contents(Filename) ->
+    {ok, Bin} = file:read_file(Filename),
+    Bin.
+
+%% Given a filename, return zip archive dir entries for each sub-dir.
+%% Required to work around issues fixed in OTP-10071.
+dir_entries(File) ->
+    Dirs = dirs(File),
+    [{Dir ++ "/", <<>>} || Dir <- Dirs].
+
+%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"].
+dirs(Dir) ->
+    dirs1(filename:split(Dir), "", []).
+
+dirs1([], _, Acc) ->
+    lists:reverse(Acc);
+dirs1([H|T], "", []) ->
+    dirs1(T, H, [H]);
+dirs1([H|T], Last, Acc) ->
+    Dir = filename:join(Last, H),
+    dirs1(T, Dir, [Dir|Acc]).
+
+usort(List) ->
+    lists:ukeysort(1, lists:flatten(List)).