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
+%% "'$(<app_name>)'"
+%% '"."' '[<options>]')</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)).