From 774f1998ed6fdb643286c9296929486ce8c6a962 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 30 Apr 2013 16:02:36 -0700 Subject: support the full range of possible arguments in the programmatic api --- src/rcl_cmd_args.erl | 94 +++++++++++++++++++++++++++++++++++++-------- src/rcl_state.erl | 1 + src/relcool.erl | 86 ++++++++++++++++++++++++++++++++--------- test/rclt_command_SUITE.erl | 12 ++++-- 4 files changed, 155 insertions(+), 38 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index bfb63b7..f5ebecd 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -21,7 +21,7 @@ %%% @doc Trivial utility file to help handle common tasks -module(rcl_cmd_args). --export([args2state/1, +-export([args2state/2, format_error/1]). -include_lib("relcool/include/relcool.hrl"). @@ -29,12 +29,10 @@ %%============================================================================ %% API %%============================================================================ --spec args2state({error, Reason::term()} | {[getopt:option()], [string()]}) -> +-spec args2state([getopt:option()], [string()]) -> {ok, {rcl_state:t(), [string()]}} | relcool:error(). -args2state({error, Detail}) -> - ?RCL_ERROR({opt_parse, Detail}); -args2state({ok, {Opts, Target}}) +args2state(Opts, Target) when erlang:length(Target) == 0; erlang:length(Target) == 1 -> RelName = proplists:get_value(relname, Opts, undefined), RelVsn = proplists:get_value(relvsn, Opts, undefined), @@ -50,16 +48,12 @@ args2state({ok, {Opts, Target}}) Error -> Error end; -args2state({ok, {_Opts, Targets}}) -> +args2state(_Opts, Targets) -> ?RCL_ERROR({invalid_targets, Targets}). -spec format_error(Reason::term()) -> iolist(). format_error({invalid_targets, Targets}) -> io_lib:format("One config must be specified! not ~p~n", [Targets]); -format_error({opt_parse, {invalid_option, Opt}}) -> - io_lib:format("invalid option ~s~n", [Opt]); -format_error({opt_parse, Arg}) -> - io_lib:format("~p~n", [Arg]); format_error({invalid_option_arg, Arg}) -> case Arg of {goals, Goal} -> @@ -77,8 +71,12 @@ format_error({invalid_option_arg, Arg}) -> end; format_error({invalid_config_file, Config}) -> io_lib:format("Invalid configuration file specified: ~s", [Config]); +format_error({invalid_caller, Caller}) -> + io_lib:format("Invalid caller specified: ~s", [Caller]); format_error({failed_to_parse, Spec}) -> io_lib:format("Unable to parse spec ~s", [Spec]); +format_error({failed_to_parse_override, QA}) -> + io_lib:format("Failed to parse app override ~s", [QA]); format_error({not_directory, Dir}) -> io_lib:format("Library directory does not exist: ~s", [Dir]); format_error({invalid_log_level, LogLevel}) -> @@ -87,7 +85,6 @@ format_error({invalid_log_level, LogLevel}) -> format_error({invalid_target, Target}) -> io_lib:format("Invalid action specified: ~s", [Target]). - %%%=================================================================== %%% Internal Functions %%%=================================================================== @@ -140,13 +137,46 @@ create_log(Opts, Acc) -> -spec create_goals([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_goals(Opts, Acc) -> - case convert_goals(proplists:get_all_values(goals, Opts), []) of + Goals = proplists:get_value(goals, Opts, []) ++ + proplists:get_all_values(goal, Opts), + case convert_goals(Goals, []) of Error={error, _} -> Error; {ok, Specs} -> - create_output_dir(Opts, [{goals, Specs} | Acc]) + create_overrides(Opts, [{goals, Specs} | Acc]) + end. + +-spec create_overrides([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_overrides(Opts, Acc) -> + Overrides = proplists:get_all_values(override, Opts) ++ + proplists:get_value(overrides, Opts, []), + case convert_overrides(Overrides, []) of + {ok, Overrides} -> + create_output_dir(Opts, [{overrides, Overrides} | Acc]); + Error -> + Error end. +-spec convert_overrides([{atom(), string() | binary()} | + string() | binary()], [{atom(), string() | binary()}]) -> + {ok, [string() | binary()]} | relcool:error(). +convert_overrides([], Acc) -> + {ok, Acc}; +convert_overrides([QA = {OverrideApp, _} | Rest], Acc) + when erlang:is_atom(OverrideApp) -> + convert_overrides(Rest, [QA | Acc]); +convert_overrides([Override | Rest], Acc) + when erlang:is_list(Override); erlang:is_binary(Override) -> + case re:split(Override, ":") of + [AppName, AppDir] -> + convert_overrides(Rest, [{erlang:iolist_to_binary(AppName), AppDir} | Acc]); + _ -> + ?RCL_ERROR({failed_to_parse_override, Override}) + end; +convert_overrides([QA | _], _) -> + ?RCL_ERROR({failed_to_parse_override, QA}). + -spec convert_goals([string()], [rcl_depsolver:constraint()]) -> {ok,[rcl_depsolver:constraint()]} | relcool:error(). @@ -154,12 +184,26 @@ convert_goals([], Specs) -> %% Reverse the specs because order matters to rcl_depsolver {ok, lists:reverse(Specs)}; convert_goals([RawSpec | Rest], Acc) -> + parse_goal(RawSpec, Rest, Acc). + +-spec parse_goal(string() | binary() | rcl_depsolver:constraint(), + [string() | binary() | rcl_depsolver:constraint()], + rcl_depsolver:constraints()) -> + {ok, rcl_depsolver:constraints()} | relcool:error(). +parse_goal(Spec, Rest, Acc) + when erlang:is_atom(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(Spec, Rest, Acc) + when erlang:is_tuple(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(RawSpec, Rest, Acc) -> case rcl_goal:parse(RawSpec) of {ok, Spec} -> convert_goals(Rest, [Spec | Acc]); {fail, _} -> ?RCL_ERROR({failed_to_parse, RawSpec}) end. + -spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_output_dir(Opts, Acc) -> @@ -169,7 +213,8 @@ create_output_dir(Opts, Acc) -> -spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_lib_dirs(Opts, Acc) -> - Dirs = proplists:get_all_values(lib_dir, Opts), + Dirs = proplists:get_all_values(lib_dir, Opts) ++ + proplists:get_value(lib_dirs, Opts, []), case check_lib_dirs(Dirs) of Error = {error, _} -> Error; @@ -193,8 +238,27 @@ create_root_dir(Opts, Acc) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_disable_default_libs(Opts, Acc) -> Def = proplists:get_value(disable_default_libs, Opts, false), - {ok, [{disable_default_libs, Def} | Acc]}. + create_caller(Opts, [{disable_default_libs, Def} | Acc]). +-spec create_caller([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_caller(Opts, Acc) -> + case proplists:get_value(caller, Opts, api) of + "command_line" -> + {ok, [{caller, command_line} | Acc]}; + "commandline" -> + {ok, [{caller, command_line} | Acc]}; + "api" -> + {ok, [{caller, api} | Acc]}; + api -> + {ok, [{caller, api} | Acc]}; + commandline -> + {ok, [{caller, command_line} | Acc]}; + command_line -> + {ok, [{caller, command_line} | Acc]}; + Caller -> + ?RCL_ERROR({invalid_caller, Caller}) + end. -spec check_lib_dirs([string()]) -> ok | relcool:error(). check_lib_dirs([]) -> ok; diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 4b21bbe..a4f091c 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -109,6 +109,7 @@ new(PropList, Target) lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], config_file=proplists:get_value(config, PropList, undefined), action = Target, + caller = proplists:get_value(caller, PropList, api), goals=proplists:get_value(goals, PropList, []), providers = [], releases=ec_dictionary:new(ec_dict), diff --git a/src/relcool.erl b/src/relcool.erl index 7750efa..0079529 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -44,13 +44,12 @@ -spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. main(Args) -> OptSpecList = opt_spec_list(), - Result = - case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of - {ok, State} -> - run_relcool_process(rcl_state:caller(State, command_line)); - Error={error, _} -> - Error - end, + Result = case getopt:parse(OptSpecList, Args) of + {ok, {Options, NonOptions}} -> + do([{caller, command_line} | Options], NonOptions); + {error, Detail} -> + ?RCL_ERROR({opt_parse, Detail}) + end, case Result of {error, _} -> report_error(rcl_state:caller(rcl_state:new([], undefined), @@ -107,25 +106,68 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> ok | error() | {ok, rcl_state:t()}. do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> - State = rcl_state:new([{relname, RelName}, - {relvsn, RelVsn}, - {goals, Goals}, - {overrides, Overrides}, - {output_dir, OutputDir}, - {lib_dirs, LibDirs}, - {root_dir, RootDir}, - {log, rcl_log:new(LogLevel)}, - {config, Config}], - release), - run_relcool_process(rcl_state:caller(State, api)). + do([{relname, RelName}, + {relvsn, RelVsn}, + {goals, Goals}, + {overrides, Overrides}, + {output_dir, OutputDir}, + {lib_dirs, LibDirs}, + {root_dir, RootDir}, + {log_level, LogLevel}, + {config, Config}], + ["release"]). +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param Opts - A proplist of options. There are good defaults for each of +%% these entries, so any or all may be omitted. Individual options may be: +%% +%%
+%%
{relname, RelName}
+%%
The release name to build
+%%
{relvsn, RelVsn}
+%%
The release version to build
+%%
{goals, Goals}
+%%
The release goals for the system in depsolver or Relcool goal +%% format (@see goals())
+%%
{lib_dirs, LibDirs}
+%%
A list of library dirs that should be used for the system
+%%
{lib_dir, LibDir}
+%%
A single lib dir that should be used for the system, may appear any +%% number of times and may be used in conjunction with lib_dirs
+%%
{output_dir, OutputDir}
+%%
The directory where the release should be built to
+%%
{root_dir, RootDir}
+%%
The base directory for this run of relcool.
+%%
{config, Config}
+%%
The path to a relcool config file
+%%
{log_level, LogLevel}
+%%
Defines the verbosity of output. Maybe a number between 0 and 2, with +%% with higher values being more verbose
+%%
{overrides, Overrides}
+%%
A list of app overrides for the system in the form of [{AppName:atom(), +%% Dir:string() | binary()} | string() | binary()] where the string or binary +%% is in the form "AppName:AppDir"
+%%
{override, Override}
+%%
A single of app override for the system in the same form as entries for +%% Overrides
+%%
+-spec do(proplists:proplist(), [string()]) -> + ok | error() | {ok, rcl_state:t()}. +do(Opts, NonOpts) -> + case rcl_cmd_args:args2state(Opts, NonOpts) of + {ok, State} -> + run_relcool_process(State); + Error={error, _} -> + Error + end. -spec opt_spec_list() -> [getopt:option_spec()]. opt_spec_list() -> [{relname, $n, "relname", string, "Specify the name for the release that will be generated"}, {relvsn, $v, "relvsn", string, "Specify the version for the release"}, - {goals, $g, "goal", string, + {goal, $g, "goal", string, "Specify a target constraint on the system. These are usually the OTP"}, {output_dir, $o, "output-dir", string, "The output directory for the release. This is `./` by default."}, @@ -136,6 +178,8 @@ opt_spec_list() -> "Disable the default system added lib dirs (means you must add them all manually"}, {log_level, $V, "verbose", {integer, 1}, "Verbosity level, maybe between 0 and 2"}, + {override_app, $a, "override_app", string, + "Provide an app name and a directory to override in the form :"}, {config, $c, "config", {string, ""}, "The path to a config file"}, {root_dir, $r, "root", string, "The project root directory"}]. @@ -143,6 +187,10 @@ opt_spec_list() -> format_error({invalid_return_value, Provider, Value}) -> [rcl_provider:format(Provider), " returned an invalid value ", io_lib:format("~p", [Value])]; +format_error({opt_parse, {invalid_option, Opt}}) -> + io_lib:format("invalid option ~s~n", [Opt]); +format_error({opt_parse, Arg}) -> + io_lib:format("~p~n", [Arg]); format_error({error, {Module, Reason}}) -> io_lib:format("~s~n", [Module:format_error(Reason)]). diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl index 9c96005..05da548 100644 --- a/test/rclt_command_SUITE.erl +++ b/test/rclt_command_SUITE.erl @@ -58,7 +58,8 @@ normal_passing_case(Config) -> RelVsn = "33.222", CmdLine = ["-V", LogLevel, "-g",Goal1,"-g",Goal2, "-l", Lib1, "-l", Lib2, "-n", RelName, "-v", RelVsn, "-o", Outdir], - {ok, State} = rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine)), + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, State} = rcl_cmd_args:args2state(Opts, Targets), ?assertMatch([Lib1, Lib2], rcl_state:lib_dirs(State)), ?assertMatch(Outdir, rcl_state:output_dir(State)), @@ -77,17 +78,20 @@ lib_fail_case(Config) -> ok = rcl_util:mkdir_p(Lib1), CmdLine = ["-l", Lib1, "-l", Lib2], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {not_directory, Lib2}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). spec_parse_fail_case(_Config) -> Spec = "aaeu:3333:33.22a44", CmdLine = ["-g", Spec], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {failed_to_parse, _Spec}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). config_fail_case(_Config) -> ConfigFile = "does-not-exist", CmdLine = ["-c", ConfigFile], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). -- cgit v1.2.3 From c2c4e59ab1f234e13617b01d612f6bbcef9fd6c7 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 30 Apr 2013 15:50:13 -0700 Subject: fix a bug in the makefile where dialyzer was not being called on test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2fc0038..fbdd672 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ ct: compile clean-common-test-data -dir $(CURDIR)/test/ \ -suite rclt_command_SUITE rclt_discover_SUITE -suite rclt_release_SUITE -test: compile eunit ct +test: compile dialyzer eunit ct $(DEPS_PLT): @echo Building local erts plt at $(DEPS_PLT) -- cgit v1.2.3 From e68bbac546e084b4949dc01cbe88c03f6c667139 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Thu, 9 May 2013 16:43:56 -0700 Subject: support a hard distinction between configured releases and realized releases --- src/rcl_prv_assembler.erl | 5 ++- src/rcl_prv_config.erl | 6 +-- src/rcl_prv_discover.erl | 6 +-- src/rcl_prv_overlay.erl | 10 ++--- src/rcl_prv_release.erl | 14 +++---- src/rcl_state.erl | 91 ++++++++++++++++++++++++++------------------- test/rclt_release_SUITE.erl | 18 ++++----- 7 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index f40bdc5..204c8bd 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -41,8 +41,8 @@ init(State) -> %% looking for OTP Applications -spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). do(State) -> - {RelName, RelVsn} = rcl_state:default_release(State), - Release = rcl_state:get_release(State, RelName, RelVsn), + {RelName, RelVsn} = rcl_state:default_configured_release(State), + Release = rcl_state:get_realized_release(State, RelName, RelVsn), OutputDir = rcl_state:output_dir(State), case create_output_dir(OutputDir) of ok -> @@ -202,6 +202,7 @@ create_release_info(State, Release, OutputDir) -> ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), case rcl_release:metadata(Release) of + State1 = rcl_state:update_realized_release(State0, Release1), {ok, Meta} -> ok = ec_file:write_term(ReleaseFile, Meta), write_bin_file(State, Release, OutputDir, ReleaseDir); diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl index dfbc321..2bc9851 100644 --- a/src/rcl_prv_config.erl +++ b/src/rcl_prv_config.erl @@ -123,7 +123,7 @@ load_config(ConfigFile, State) -> -spec load_terms(term(), {ok, rcl_state:t()} | relcool:error()) -> {ok, rcl_state:t()} | relcool:error(). load_terms({default_release, RelName, RelVsn}, {ok, State}) -> - {ok, rcl_state:default_release(State, RelName, RelVsn)}; + {ok, rcl_state:default_configured_release(State, RelName, RelVsn)}; load_terms({paths, Paths}, {ok, State}) -> code:add_pathsa([filename:absname(Path) || Path <- Paths]), {ok, State}; @@ -154,7 +154,7 @@ load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) -> E={error, _} -> E; {ok, Release1} -> - {ok, rcl_state:add_release(State0, Release1)} + {ok, rcl_state:add_configured_release(State0, Release1)} end; load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications}, {ok, State}) -> @@ -163,7 +163,7 @@ load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, E={error, _} -> E; {ok, Release1} -> - {ok, rcl_state:add_release(State, Release1)} + {ok, rcl_state:add_configured_release(State, Release1)} end; load_terms({sys_config, SysConfig}, {ok, State}) -> {ok, rcl_state:sys_config(State, filename:absname(SysConfig))}; diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 3627787..56085ac 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -48,9 +48,9 @@ do(State0) -> case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of {ok, Releases} -> State1 = rcl_state:available_apps(State0, AppMeta), - {ok, rcl_state:discovered_releases(State1, lists:foldl(fun add/2, - ec_dictionary:new(ec_dict), - Releases))}; + {ok, rcl_state:realized_releases(State1, lists:foldl(fun add/2, + ec_dictionary:new(ec_dict), + Releases))}; Error -> Error end; diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 40471ea..35e37bf 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -43,8 +43,8 @@ init(State) -> %% looking for OTP Applications -spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). do(State) -> - {RelName, RelVsn} = rcl_state:default_release(State), - Release = rcl_state:get_release(State, RelName, RelVsn), + {RelName, RelVsn} = rcl_state:default_configured_release(State), + Release = rcl_state:get_realized_release(State, RelName, RelVsn), case rcl_release:realized(Release) of true -> generate_overlay_vars(State, Release); @@ -202,15 +202,15 @@ generate_state_vars(State) -> {providers, rcl_state:providers(State)}, {sys_config, rcl_state:sys_config(State)}, {root_dir, rcl_state:root_dir(State)}, - {default_release_name, case rcl_state:default_release(State) of + {default_release_name, case rcl_state:default_configured_release(State) of {Name0, _} -> Name0 end}, - {default_release_version, case rcl_state:default_release(State) of + {default_release_version, case rcl_state:default_configured_release(State) of {_, Vsn0} -> Vsn0 end}, - {default_release, case rcl_state:default_release(State) of + {default_release, case rcl_state:default_configured_release(State) of {Name1, undefined} -> erlang:atom_to_list(Name1); {Name1, Vsn1} -> diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl index eac1f20..d90b492 100644 --- a/src/rcl_prv_release.erl +++ b/src/rcl_prv_release.erl @@ -89,7 +89,7 @@ create_dep_graph(State) -> -spec find_default_release(rcl_state:t(), rcl_depsolver:t()) -> {ok, rcl_state:t()} | relcool:error(). find_default_release(State, DepGraph) -> - case rcl_state:default_release(State) of + case rcl_state:default_configured_release(State) of {undefined, undefined} -> resolve_default_release(State, DepGraph); {RelName, undefined} -> @@ -103,9 +103,9 @@ find_default_release(State, DepGraph) -> resolve_default_release(State0, DepGraph) -> %% Here we will just get the highest versioned release and run that. case lists:sort(fun release_sort/2, - ec_dictionary:to_list(rcl_state:releases(State0))) of + ec_dictionary:to_list(rcl_state:configured_releases(State0))) of [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_release(State0, RelName, RelVsn), + State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), solve_release(State1, DepGraph, RelName, RelVsn); [] -> ?RCL_ERROR(no_releases_in_system) @@ -113,12 +113,12 @@ resolve_default_release(State0, DepGraph) -> resolve_default_version(State0, DepGraph, RelName) -> %% Here we will just get the lastest version and run that. - AllReleases = ec_dictionary:to_list(rcl_state:releases(State0)), + AllReleases = ec_dictionary:to_list(rcl_state:configured_releases(State0)), SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases, PossibleRelName =:= RelName], case lists:sort(fun release_sort/2, SpecificReleases) of [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_release(State0, RelName, RelVsn), + State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), solve_release(State1, DepGraph, RelName, RelVsn); [] -> ?RCL_ERROR({no_releases_for, RelName}) @@ -143,7 +143,7 @@ solve_release(State0, DepGraph, RelName, RelVsn) -> "Solving Release ~p-~s~n", [RelName, RelVsn]), try - Release = rcl_state:get_release(State0, RelName, RelVsn), + Release = rcl_state:get_configured_release(State0, RelName, RelVsn), Goals = rcl_release:goals(Release), case Goals of [] -> @@ -172,7 +172,7 @@ set_resolved(State, Release0, Pkgs) -> fun() -> rcl_release:format(1, Release1) end), - {ok, rcl_state:update_release(State, Release1)}; + {ok, rcl_state:add_realized_release(State, Release1)}; {error, E} -> ?RCL_ERROR({release_error, E}) end. diff --git a/src/rcl_state.erl b/src/rcl_state.erl index a4f091c..3a91f48 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -40,14 +40,16 @@ sys_config/2, root_dir/1, root_dir/2, - add_release/2, - get_release/3, - update_release/2, - releases/1, - discovered_releases/1, - discovered_releases/2, - default_release/1, - default_release/3, + add_configured_release/2, + get_configured_release/3, + configured_releases/1, + realized_releases/1, + realized_releases/2, + add_realized_release/2, + get_realized_release/3, + update_realized_release/2, + default_configured_release/1, + default_configured_release/3, available_apps/1, available_apps/2, get/2, @@ -73,12 +75,13 @@ goals=[] :: [rcl_depsolver:constraint()], providers = [] :: [rcl_provider:t()], available_apps = [] :: [rcl_app_info:t()], - default_release :: {rcl_release:name(), rcl_release:vsn()}, + default_configured_release :: {rcl_release:name(), rcl_release:vsn()}, sys_config :: file:filename() | undefined, overrides :: [{AppName::atom(), Directory::file:filename()}], skip_apps = [] :: [AppName::atom()], - releases :: releases(), - discovered_releases :: releases(), + configured_releases :: releases(), + realized_releases :: releases(), + upfrom :: string() | binary() | undefined, config_values :: ec_dictionary:dictionary(Key::atom(), Value::term())}). @@ -112,12 +115,13 @@ new(PropList, Target) caller = proplists:get_value(caller, PropList, api), goals=proplists:get_value(goals, PropList, []), providers = [], - releases=ec_dictionary:new(ec_dict), - discovered_releases=ec_dictionary:new(ec_dict), + configured_releases=ec_dictionary:new(ec_dict), + realized_releases=ec_dictionary:new(ec_dict), config_values=ec_dictionary:new(ec_dict), overrides = proplists:get_value(overrides, PropList, []), root_dir = proplists:get_value(root_dir, PropList, Root), - default_release={proplists:get_value(relname, PropList, undefined), + upfrom = proplists:get_value(upfrom, PropList, undefined), + default_configured_release={proplists:get_value(relname, PropList, undefined), proplists:get_value(relvsn, PropList, undefined)}}, rcl_state:put(create_logic_providers(State0), disable_default_libs, @@ -192,44 +196,55 @@ root_dir(State, RootDir) -> providers(M, NewProviders) -> M#state_t{providers=NewProviders}. --spec add_release(t(), rcl_release:t()) -> t(). -add_release(M=#state_t{releases=Releases}, Release) -> - M#state_t{releases=ec_dictionary:add({rcl_release:name(Release), +-spec add_configured_release(t(), rcl_release:t()) -> t(). +add_configured_release(M=#state_t{configured_releases=Releases}, Release) -> + M#state_t{configured_releases=ec_dictionary:add({rcl_release:name(Release), rcl_release:vsn(Release)}, Release, Releases)}. --spec update_release(t(), rcl_release:t()) -> t(). -update_release(M=#state_t{releases=Releases}, Release) -> - M#state_t{releases=ec_dictionary:add({rcl_release:name(Release), - rcl_release:vsn(Release)}, - Release, - Releases)}. - --spec get_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). -get_release(#state_t{releases=Releases}, Name, Vsn) -> +-spec get_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +get_configured_release(#state_t{configured_releases=Releases}, Name, Vsn) -> ec_dictionary:get({Name, Vsn}, Releases). --spec releases(t()) -> releases(). -releases(#state_t{releases=Releases}) -> +-spec configured_releases(t()) -> releases(). +configured_releases(#state_t{configured_releases=Releases}) -> Releases. --spec discovered_releases(t()) -> releases(). -discovered_releases(#state_t{discovered_releases=Releases}) -> +-spec realized_releases(t()) -> releases(). +realized_releases(#state_t{realized_releases=Releases}) -> Releases. --spec discovered_releases(t(), releases()) -> t(). -discovered_releases(State, Releases) -> - State#state_t{discovered_releases=Releases}. +-spec realized_releases(t(), releases()) -> t(). +realized_releases(State, Releases) -> + State#state_t{realized_releases=Releases}. + +-spec add_realized_release(t(), rcl_release:t()) -> t(). +add_realized_release(State = #state_t{realized_releases=Releases}, Release) -> + NewReleases = ec_dictionary:add({rcl_release:name(Release), rcl_release:vsn(Release)}, + Release, Releases), + State#state_t{realized_releases=NewReleases}. + +-spec get_realized_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +get_realized_release(#state_t{realized_releases=Releases}, Name, Vsn) -> + ec_dictionary:get({Name, Vsn}, Releases). + +-spec update_realized_release(t(), rcl_release:t()) -> + t(). +update_realized_release(M=#state_t{realized_releases=Releases}, Release) -> + M#state_t{realized_releases=ec_dictionary:add({rcl_release:name(Release), + rcl_release:vsn(Release)}, + Release, + Releases)}. --spec default_release(t()) -> +-spec default_configured_release(t()) -> {rcl_release:name() | undefined, rcl_release:vsn() | undefined}. -default_release(#state_t{default_release=Def}) -> +default_configured_release(#state_t{default_configured_release=Def}) -> Def. --spec default_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). -default_release(M, Name, Vsn) -> - M#state_t{default_release={Name, Vsn}}. +-spec default_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). +default_configured_release(M, Name, Vsn) -> + M#state_t{default_configured_release={Name, Vsn}}. -spec available_apps(t()) -> [rcl_app_info:t()]. available_apps(#state_t{available_apps=Apps}) -> diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index f0446d6..97f564e 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -92,7 +92,7 @@ make_release(Config) -> create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -159,7 +159,7 @@ make_scriptless_release(Config) -> ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo"]))), ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo-0.0.1"]))), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -206,7 +206,7 @@ make_overridden_release(Config) -> {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -255,7 +255,7 @@ make_skip_app_release(Config) -> {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -295,7 +295,7 @@ make_implicit_config_release(Config) -> {ok, FooRoot} = file:get_cwd(), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, OutputDir, undefined), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), ?assert(ec_file:exists(OutputDir)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), @@ -349,7 +349,7 @@ make_rerun_overridden_release(Config) -> OutputDir, [{OverrideAppName, OverrideAppDir}], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -412,7 +412,7 @@ overlay_release(Config) -> {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), @@ -547,7 +547,7 @@ make_depfree_release(Config) -> create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)). @@ -632,7 +632,7 @@ make_one_app_top_level_release(Config) -> {ok, State} = relcool:do(undefined, undefined, [], [], 2, OutputDir, ConfigFile), ok = file:set_cwd(Cwd), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), AppSpecs = rcl_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), -- cgit v1.2.3 From 0d5a803a28010cc956948b614408b9d38997e9a1 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Fri, 3 May 2013 09:17:35 -0700 Subject: support the creation or relups --- src/rcl_cmd_args.erl | 12 +++- src/rcl_prv_assembler.erl | 158 +++++++++++++++++++++++++++++++++++++------- src/rcl_rel_discovery.erl | 6 +- src/rcl_release.erl | 30 ++++++++- src/rcl_state.erl | 11 +++ src/rcl_util.erl | 3 + src/relcool.erl | 3 + test/rclt_release_SUITE.erl | 138 ++++++++++++++++++++++++++++++++++---- 8 files changed, 319 insertions(+), 42 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index f5ebecd..fee4449 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -238,7 +238,17 @@ create_root_dir(Opts, Acc) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_disable_default_libs(Opts, Acc) -> Def = proplists:get_value(disable_default_libs, Opts, false), - create_caller(Opts, [{disable_default_libs, Def} | Acc]). + create_upfrom(Opts, [{disable_default_libs, Def} | Acc]). + +-spec create_upfrom([getopt:option()], rcl:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_upfrom(Opts, Acc) -> + case proplists:get_value(upfrom, Opts, undefined) of + undefined -> + create_caller(Opts, Acc); + UpFrom -> + create_caller(Opts, [{upfrom, UpFrom} | Acc]) + end. -spec create_caller([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 204c8bd..aca783d 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -80,6 +80,20 @@ format_error({unable_to_create_output_dir, OutputDir}) -> format_error({release_script_generation_error, Module, Errors}) -> ["Errors generating release \n", rcl_util:indent(1), Module:format_error(Errors)]; +format_error({relup_generation_error, CurrentName, UpFromName}) -> + io_lib:format("Unknown internal release error generating the relup from ~s to ~s", + [UpFromName, CurrentName]); +format_error({relup_generation_warning, Module, Warnings}) -> + ["Warnings generating relup \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({relup_script_generation_error, + {relupcript_generation_error, systools_relup, + {missing_sasl, _}}}) -> + "Unfortunately, due to requirements in systools, you need to have the sasl application " + "in both the current release and the release to upgrade from."; +format_error({relup_script_generation_error, Module, Errors}) -> + ["Errors generating relup \n", + rcl_util:indent(1), Module:format_error(Errors)]; format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", [AppDir, TargetDir, rcl_util:indent(1), @@ -193,19 +207,17 @@ copy_dir(AppDir, TargetDir, SubDir) -> ok end. -create_release_info(State, Release, OutputDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release)), - ReleaseDir = filename:join([OutputDir, - "releases", - RelName ++ "-" ++ - rcl_release:vsn(Release)]), +create_release_info(State0, Release0, OutputDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release0)), + ReleaseDir = release_output_dir(State0, Release0), ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), - case rcl_release:metadata(Release) of + Release1 = rcl_release:relfile(Release0, ReleaseFile), State1 = rcl_state:update_realized_release(State0, Release1), + case rcl_release:metadata(Release1) of {ok, Meta} -> ok = ec_file:write_term(ReleaseFile, Meta), - write_bin_file(State, Release, OutputDir, ReleaseDir); + write_bin_file(State1, Release1, OutputDir, ReleaseDir); E -> E end. @@ -216,7 +228,7 @@ write_bin_file(State, Release, OutputDir, RelDir) -> RelVsn = rcl_release:vsn(Release), BinDir = filename:join([OutputDir, "bin"]), ok = ec_file:mkdir_p(BinDir), - VsnRel = filename:join(BinDir, RelName ++ "-" ++ RelVsn), + VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), BareRel = filename:join(BinDir, RelName), ErlOpts = rcl_state:get(State, erl_opts, ""), StartFile = case rcl_state:get(State, extended_start_script, false) of @@ -320,42 +332,142 @@ make_boot_script(State, Release, OutputDir, RelDir) -> no_module_tests, silent], Name = erlang:atom_to_list(rcl_release:name(Release)), ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), - rcl_log:debug(rcl_state:log(State), - "Creating script from release file ~s ~n with options ~p ~n", - [ReleaseFile, Options]), - case make_script(Name, Options) of + case make_script(Options, + fun(CorrectedOptions) -> + systools:make_script(Name, CorrectedOptions) + end) of ok -> rcl_log:error(rcl_state:log(State), "release successfully created!"), - {ok, State}; + make_relup(State, Release); error -> ?RCL_ERROR({release_script_generation_error, ReleaseFile}); {ok, _, []} -> rcl_log:error(rcl_state:log(State), "release successfully created!"), - {ok, State}; + make_relup(State, Release); {ok,Module,Warnings} -> ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); {error,Module,Error} -> ?RCL_ERROR({release_script_generation_error, Module, Error}) end. --spec make_script(string(), [term()]) -> - ok | - error | - {ok, module(), [term()]} | - {error,module,[term()]}. -make_script(Name, Options) -> +-spec make_script([term()], + fun(([term()]) -> Res)) -> Res. +make_script(Options, RunFun) -> %% Erts 5.9 introduced a non backwards compatible option to %% erlang this takes that into account Erts = erlang:system_info(version), case ec_semver:gte(Erts, "5.9") of true -> - systools:make_script(Name, [no_warn_sasl | Options]); + RunFun([no_warn_sasl | Options]); + _ -> + RunFun(Options) + end. + +make_relup(State, Release) -> + case rcl_state:action(State) of + relup -> + UpFrom = + case rcl_state:upfrom(State) of + undefined -> + get_last_release(State, Release); + Vsn -> + get_up_release(State, Release, Vsn) + end, + case UpFrom of + undefined -> + ?RCL_ERROR(no_upfrom_release_found); + _ -> + make_upfrom_script(State, Release, UpFrom) + end; _ -> - systools:make_script(Name, Options) + {ok, State} end. +make_upfrom_script(State, Release, UpFrom) -> + OutputDir = rcl_state:output_dir(State), + Options = [{outdir, OutputDir}, + {path, get_code_paths(Release, OutputDir) ++ + get_code_paths(UpFrom, OutputDir)}, + silent], + CurrentRel = strip_rel(rcl_release:relfile(Release)), + UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), + rcl_log:debug(rcl_state:log(State), + "systools:make_relup(~p, ~p, ~p, ~p)", + [CurrentRel, UpFromRel, UpFromRel, Options]), + case make_script(Options, + fun(CorrectOptions) -> + systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "relup from ~s to ~s successfully created!", + [UpFromRel, CurrentRel]), + {ok, State}; + error -> + ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); + {ok, RelUp, _, []} -> + rcl_log:error(rcl_state:log(State), + "relup successfully created!"), + write_relup_file(State, Release, RelUp), + {ok, State}; + {ok,_, Module,Warnings} -> + ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); + {error,Module,Errors} -> + ?RCL_ERROR({relupcript_generation_error, Module, Errors}) + end. + +write_relup_file(State, Release, Relup) -> + OutDir = release_output_dir(State, Release), + RelName = rcl_util:to_string(rcl_release:name(Release)), + RelupFile = filename:join(OutDir, RelName ++ ".relup"), + ok = ec_file:write_term(RelupFile, Relup). + +strip_rel(Name) -> + rcl_util:to_string(filename:join(filename:dirname(Name), + filename:basename(Name, ".rel"))). + + +get_up_release(State, Release, Vsn) -> + Name = rcl_release:name(Release), + try + ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) + catch + throw:notfound -> + undefined + end. + +get_last_release(State, Release) -> + Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], + Releases1 = lists:sort(fun(R1, R2) -> + ec_semver:lte(rcl_release:vsn(R1), + rcl_release:vsn(R2)) + end, Releases0), + Res = lists:foldl(fun(_Rel, R = {found, _}) -> + R; + (Rel, Prev) -> + case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of + true -> + {found, Prev}; + false -> + Rel + end + end, undefined, Releases1), + case Res of + {found, R} -> + R; + Else -> + Else + end. + +-spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). +release_output_dir(State, Release) -> + OutputDir = rcl_state:output_dir(State), + filename:join([OutputDir, + "releases", + rcl_release:canonical_name(Release)]). + %% @doc Generates the correct set of code paths for the system. -spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. get_code_paths(Release, OutDir) -> diff --git a/src/rcl_rel_discovery.erl b/src/rcl_rel_discovery.erl index d9012ea..6cd84f0 100644 --- a/src/rcl_rel_discovery.erl +++ b/src/rcl_rel_discovery.erl @@ -118,15 +118,15 @@ resolve_release(RelFile, AppMeta) -> {ok, [{release, {RelName, RelVsn}, {erts, ErtsVsn}, Apps}]} -> - build_release(RelName, RelVsn, ErtsVsn, Apps, AppMeta); + build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta); {ok, InvalidRelease} -> ?RCL_ERROR({invalid_release_information, InvalidRelease}); {error, Reason} -> ?RCL_ERROR({unable_to_read, RelFile, Reason}) end. -build_release(RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> - Release = rcl_release:erts(rcl_release:new(RelName, RelVsn), +build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> + Release = rcl_release:erts(rcl_release:new(RelName, RelVsn, RelFile), ErtsVsn), resolve_apps(Apps, AppMeta, Release, []). diff --git a/src/rcl_release.erl b/src/rcl_release.erl index 97465d0..9ed741e 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -23,6 +23,9 @@ -module(rcl_release). -export([new/2, + new/3, + relfile/1, + relfile/2, erts/2, erts/1, goals/2, @@ -35,6 +38,7 @@ application_details/2, realized/1, metadata/1, + canonical_name/1, format/1, format/2, format_error/1]). @@ -57,6 +61,7 @@ realized = false :: boolean(), annotations = undefined :: annotations(), applications = [] :: [application_spec()], + relfile :: undefined | string(), app_detail = [] :: [rcl_app_info:t()]}). %%============================================================================ @@ -87,11 +92,25 @@ %%============================================================================ %% API %%============================================================================ --spec new(atom(), string()) -> t(). -new(ReleaseName, ReleaseVsn) -> +-spec new(atom(), string(), undefined | file:name()) -> t(). +new(ReleaseName, ReleaseVsn, Relfile) -> #release_t{name=to_atom(ReleaseName), vsn=ReleaseVsn, + relfile = Relfile, annotations=ec_dictionary:new(ec_dict)}. +-spec new(atom(), string()) -> t(). +new(ReleaseName, ReleaseVsn) -> + new(ReleaseName, ReleaseVsn, undefined). + + +-spec relfile(t()) -> file:name() | undefined. +relfile(#release_t{relfile=Relfile}) -> + Relfile. + +-spec relfile(t(), file:name()) -> t(). +relfile(Release, Relfile) -> + Release#release_t{relfile=Relfile}. + -spec name(t()) -> atom(). name(#release_t{name=Name}) -> Name. @@ -162,6 +181,12 @@ metadata(#release_t{name=Name, vsn=Vsn, erts=ErtsVsn, applications=Apps, ?RCL_ERROR({not_realized, Name, Vsn}) end. +%% @doc produce the canonical name (-) for this release +-spec canonical_name(t()) -> string(). +canonical_name(#release_t{name=Name, vsn=Vsn}) -> + erlang:binary_to_list(erlang:iolist_to_binary([erlang:atom_to_list(Name), "-", + Vsn])). + -spec format(t()) -> iolist(). format(Release) -> format(0, Release). @@ -183,6 +208,7 @@ format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, false -> [] end]. + -spec format_goal(application_goal()) -> iolist(). format_goal({Constraint, AppType}) -> io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType}]); diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 3a91f48..ca6ec8c 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -25,6 +25,7 @@ -export([new/2, log/1, + action/1, output_dir/1, lib_dirs/1, overrides/1, @@ -57,6 +58,7 @@ put/3, caller/1, caller/2, + upfrom/1, format/1, format/2]). @@ -127,6 +129,11 @@ new(PropList, Target) disable_default_libs, proplists:get_value(disable_default_libs, PropList, false)). +%% @doc the action targeted for this system +-spec action(t()) -> atom(). +action(#state_t{action=Action}) -> + Action. + %% @doc the application overrides for the system -spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}]. overrides(#state_t{overrides=Overrides}) -> @@ -282,6 +289,10 @@ caller(#state_t{caller=Caller}) -> caller(S, Caller) -> S#state_t{caller=Caller}. +-spec upfrom(t()) -> string() | binary() | undefined. +upfrom(#state_t{upfrom=UpFrom}) -> + UpFrom. + -spec format(t()) -> iolist(). format(Mod) -> format(Mod, 0). diff --git a/src/rcl_util.erl b/src/rcl_util.erl index 61e1392..4afb26d 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -58,6 +58,9 @@ to_binary(String) when erlang:is_list(String) -> erlang:iolist_to_binary(String); to_binary(Bin) when erlang:is_binary(Bin) -> Bin. + +to_string(Binary) when erlang:is_binary(Binary) -> + erlang:binary_to_list(Binary); to_string(Atom) when erlang:is_atom(Atom) -> erlang:atom_to_list(Atom); to_string(Else) when erlang:is_list(Else) -> diff --git a/src/relcool.erl b/src/relcool.erl index 0079529..c404e6f 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -21,6 +21,7 @@ -module(relcool). -export([main/1, + do/2, do/7, do/8, do/9, @@ -169,6 +170,8 @@ opt_spec_list() -> {relvsn, $v, "relvsn", string, "Specify the version for the release"}, {goal, $g, "goal", string, "Specify a target constraint on the system. These are usually the OTP"}, + {upfrom, $u, "upfrom", string, + "Only valid with relup target, specify the release to upgrade from"}, {output_dir, $o, "output-dir", string, "The output directory for the release. This is `./` by default."}, {lib_dir, $l, "lib-dir", string, diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 97f564e..90d99c9 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -35,6 +35,7 @@ make_depfree_release/1, make_invalid_config_release/1, make_relup_release/1, + make_relup_release2/1, make_one_app_top_level_release/1]). -include_lib("common_test/include/ct.hrl"). @@ -64,7 +65,7 @@ all() -> make_skip_app_release, make_implicit_config_release, make_rerun_overridden_release, overlay_release, make_goalless_release, make_depfree_release, - make_invalid_config_release, make_relup_release, + make_invalid_config_release, make_relup_release, make_relup_release2, make_one_app_top_level_release]. make_release(Config) -> @@ -564,24 +565,30 @@ make_relup_release(Config) -> create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + write_appup_file(GA1, "0.0.2"), + write_appup_file(GA2, "0.0.2"), + ConfigFile = filename:join([LibDir1, "relcool.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, - [{goal_app_1, "0.0.1"}, + [sasl, + {goal_app_1, "0.0.1"}, {goal_app_2, "0.0.1"}]}, {release, {foo, "0.0.2"}, - [{goal_app_1, "0.0.2"}, + [sasl, + {goal_app_1, "0.0.2"}, {goal_app_2, "0.0.2"}]}, {release, {foo, "0.0.3"}, - [{goal_app_1, "0.0.3"}, + [sasl, + {goal_app_1, "0.0.3"}, {goal_app_2, "0.0.3"}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), create_random_name("relcool-output")]), @@ -589,20 +596,116 @@ make_relup_release(Config) -> OutputDir, ConfigFile), {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, OutputDir, ConfigFile), - {ok, State} = relcool:do(foo, "0.0.3", [], [LibDir1], 2, - OutputDir, ConfigFile), + {ok, State} = relcool:do([{relname, foo}, + {relvsn, "0.0.3"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 2}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["relup"]), + + %% we should have one 'resolved' release and three discovered realized_releases. + ?assertMatch([{foo, "0.0.1"}, + {foo, "0.0.2"}, + {foo, "0.0.3"}], + lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), + ?assert(rcl_release:realized(Release)), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rcl_state:realized_releases(State)))), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rcl_state:realized_releases(State)))), + + ?assertMatch({ok, [{"0.0.3", + [{"0.0.2",[],[point_of_no_return]}], + [{"0.0.2",[],[point_of_no_return]}]}]}, + file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), + filename:basename(rcl_release:relfile(Release), ".rel") ++ + ".relup"))), + + ?assertMatch(foo, rcl_release:name(Release)), + ?assertMatch("0.0.3", rcl_release:vsn(Release)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.3"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.3"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + + +make_relup_release2(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], - %% we should have one 'resolved' release and three discovered releases. + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + write_appup_file(GA1, "0.0.1"), + write_appup_file(GA2, "0.0.1"), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [sasl, + {goal_app_1, "0.0.1"}, + {goal_app_2, "0.0.1"}]}, + {release, {foo, "0.0.2"}, + [sasl, + {goal_app_1, "0.0.2"}, + {goal_app_2, "0.0.2"}]}, + {release, {foo, "0.0.3"}, + [sasl, + {goal_app_1, "0.0.3"}, + {goal_app_2, "0.0.3"}]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, State} = relcool:do([{relname, foo}, + {relvsn, "0.0.3"}, + {upfrom, "0.0.1"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 2}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["relup"]), + + %% we should have one 'resolved' release and three discovered realized_releases. ?assertMatch([{foo, "0.0.1"}, {foo, "0.0.2"}, {foo, "0.0.3"}], - lists:sort(ec_dictionary:keys(rcl_state:releases(State)))), - Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:releases(State)), + lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), ?assert(rcl_release:realized(Release)), ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, - rcl_state:releases(State)))), + rcl_state:realized_releases(State)))), ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, - rcl_state:releases(State)))), + rcl_state:realized_releases(State)))), + + ?assertMatch({ok, [{"0.0.3", + [{"0.0.1",[],[point_of_no_return]}], + [{"0.0.1",[],[point_of_no_return]}]}]}, + file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), + filename:basename(rcl_release:relfile(Release), ".rel") ++ + ".relup"))), + ?assertMatch(foo, rcl_release:name(Release)), ?assertMatch("0.0.3", rcl_release:vsn(Release)), AppSpecs = rcl_release:applications(Release), @@ -642,6 +745,7 @@ make_one_app_top_level_release(Config) -> %%%=================================================================== %%% Helper Functions %%%=================================================================== + create_app(Dir, Name, Vsn, Deps, LibDeps) -> AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), write_app_file(AppDir, Name, Vsn, Deps, LibDeps), @@ -660,6 +764,14 @@ write_beam_file(Dir, Name) -> ok = filelib:ensure_dir(Beam), ok = ec_file:write_term(Beam, testing_purposes_only). +write_appup_file(AppInfo, DownVsn) -> + Dir = rcl_app_info:dir(AppInfo), + Name = rcl_util:to_string(rcl_app_info:name(AppInfo)), + Vsn = rcl_app_info:vsn_as_string(AppInfo), + Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). + write_app_file(Dir, Name, Version, Deps, LibDeps) -> Filename = filename:join([Dir, "ebin", Name ++ ".app"]), ok = filelib:ensure_dir(Filename), -- cgit v1.2.3 From efbdfbe117939cd50d0252a210b9b7634de42bb4 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 May 2013 17:04:18 -0700 Subject: Basic file rename from rcl to rlx --- include/relcool.hrl | 27 -- include/relx.hrl | 27 ++ relcool.config | 3 - relx.config | 3 + src/rcl_app_discovery.erl | 207 --------- src/rcl_app_info.erl | 193 -------- src/rcl_cmd_args.erl | 281 ------------ src/rcl_depsolver.erl | 718 ----------------------------- src/rcl_depsolver_culprit.erl | 359 --------------- src/rcl_dscv_util.erl | 142 ------ src/rcl_goal.peg | 55 --- src/rcl_goal_utils.erl | 77 ---- src/rcl_log.erl | 204 --------- src/rcl_provider.erl | 117 ----- src/rcl_prv_assembler.erl | 1003 ----------------------------------------- src/rcl_prv_config.erl | 186 -------- src/rcl_prv_discover.erl | 133 ------ src/rcl_prv_overlay.erl | 418 ----------------- src/rcl_prv_release.erl | 187 -------- src/rcl_rel_discovery.erl | 156 ------- src/rcl_release.erl | 415 ----------------- src/rcl_state.erl | 354 --------------- src/rcl_topo.erl | 217 --------- src/rcl_util.erl | 93 ---- src/relcool.app.src | 26 -- src/relcool.erl | 278 ------------ src/relx.app.src | 26 ++ src/relx.erl | 278 ++++++++++++ src/rlx_app_discovery.erl | 207 +++++++++ src/rlx_app_info.erl | 193 ++++++++ src/rlx_cmd_args.erl | 281 ++++++++++++ src/rlx_depsolver.erl | 718 +++++++++++++++++++++++++++++ src/rlx_depsolver_culprit.erl | 359 +++++++++++++++ src/rlx_dscv_util.erl | 142 ++++++ src/rlx_goal.erl | 263 +++++++++++ src/rlx_goal.peg | 55 +++ src/rlx_goal_utils.erl | 77 ++++ src/rlx_log.erl | 204 +++++++++ src/rlx_provider.erl | 117 +++++ src/rlx_prv_assembler.erl | 1003 +++++++++++++++++++++++++++++++++++++++++ src/rlx_prv_config.erl | 186 ++++++++ src/rlx_prv_discover.erl | 133 ++++++ src/rlx_prv_overlay.erl | 418 +++++++++++++++++ src/rlx_prv_release.erl | 187 ++++++++ src/rlx_rel_discovery.erl | 156 +++++++ src/rlx_release.erl | 415 +++++++++++++++++ src/rlx_state.erl | 354 +++++++++++++++ src/rlx_topo.erl | 217 +++++++++ src/rlx_util.erl | 93 ++++ test/rcl_depsolver_tester.erl | 474 ------------------- test/rcl_depsolver_tests.erl | 495 -------------------- test/rclt_command_SUITE.erl | 97 ---- test/rclt_discover_SUITE.erl | 193 -------- test/rclt_goal.erl | 63 --- test/rclt_release_SUITE.erl | 837 ---------------------------------- test/rlx_command_SUITE.erl | 97 ++++ test/rlx_depsolver_tester.erl | 474 +++++++++++++++++++ test/rlx_depsolver_tests.erl | 495 ++++++++++++++++++++ test/rlx_discover_SUITE.erl | 193 ++++++++ test/rlx_goal_tests.erl | 63 +++ test/rlx_release_SUITE.erl | 837 ++++++++++++++++++++++++++++++++++ 61 files changed, 8271 insertions(+), 8008 deletions(-) delete mode 100644 include/relcool.hrl create mode 100644 include/relx.hrl delete mode 100644 relcool.config create mode 100644 relx.config delete mode 100644 src/rcl_app_discovery.erl delete mode 100644 src/rcl_app_info.erl delete mode 100644 src/rcl_cmd_args.erl delete mode 100644 src/rcl_depsolver.erl delete mode 100644 src/rcl_depsolver_culprit.erl delete mode 100644 src/rcl_dscv_util.erl delete mode 100644 src/rcl_goal.peg delete mode 100644 src/rcl_goal_utils.erl delete mode 100644 src/rcl_log.erl delete mode 100644 src/rcl_provider.erl delete mode 100644 src/rcl_prv_assembler.erl delete mode 100644 src/rcl_prv_config.erl delete mode 100644 src/rcl_prv_discover.erl delete mode 100644 src/rcl_prv_overlay.erl delete mode 100644 src/rcl_prv_release.erl delete mode 100644 src/rcl_rel_discovery.erl delete mode 100644 src/rcl_release.erl delete mode 100644 src/rcl_state.erl delete mode 100644 src/rcl_topo.erl delete mode 100644 src/rcl_util.erl delete mode 100644 src/relcool.app.src delete mode 100644 src/relcool.erl create mode 100644 src/relx.app.src create mode 100644 src/relx.erl create mode 100644 src/rlx_app_discovery.erl create mode 100644 src/rlx_app_info.erl create mode 100644 src/rlx_cmd_args.erl create mode 100644 src/rlx_depsolver.erl create mode 100644 src/rlx_depsolver_culprit.erl create mode 100644 src/rlx_dscv_util.erl create mode 100644 src/rlx_goal.erl create mode 100644 src/rlx_goal.peg create mode 100644 src/rlx_goal_utils.erl create mode 100644 src/rlx_log.erl create mode 100644 src/rlx_provider.erl create mode 100644 src/rlx_prv_assembler.erl create mode 100644 src/rlx_prv_config.erl create mode 100644 src/rlx_prv_discover.erl create mode 100644 src/rlx_prv_overlay.erl create mode 100644 src/rlx_prv_release.erl create mode 100644 src/rlx_rel_discovery.erl create mode 100644 src/rlx_release.erl create mode 100644 src/rlx_state.erl create mode 100644 src/rlx_topo.erl create mode 100644 src/rlx_util.erl delete mode 100644 test/rcl_depsolver_tester.erl delete mode 100644 test/rcl_depsolver_tests.erl delete mode 100644 test/rclt_command_SUITE.erl delete mode 100644 test/rclt_discover_SUITE.erl delete mode 100644 test/rclt_goal.erl delete mode 100644 test/rclt_release_SUITE.erl create mode 100644 test/rlx_command_SUITE.erl create mode 100644 test/rlx_depsolver_tester.erl create mode 100644 test/rlx_depsolver_tests.erl create mode 100644 test/rlx_discover_SUITE.erl create mode 100644 test/rlx_goal_tests.erl create mode 100644 test/rlx_release_SUITE.erl diff --git a/include/relcool.hrl b/include/relcool.hrl deleted file mode 100644 index 11a0dec..0000000 --- a/include/relcool.hrl +++ /dev/null @@ -1,27 +0,0 @@ -%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% - --define(RCL_ERROR, 0). --define(RCL_INFO, 1). --define(RCL_DEBUG, 2). - -%% This is the default form of error messages for the Relcool -%% system. It is expected that everything that returns an error use -%% this and that they all expose a format_error/1 message that returns -%% an iolist. --define(RCL_ERROR(Reason), - {error, {?MODULE, Reason}}). diff --git a/include/relx.hrl b/include/relx.hrl new file mode 100644 index 0000000..11a0dec --- /dev/null +++ b/include/relx.hrl @@ -0,0 +1,27 @@ +%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% + +-define(RCL_ERROR, 0). +-define(RCL_INFO, 1). +-define(RCL_DEBUG, 2). + +%% This is the default form of error messages for the Relcool +%% system. It is expected that everything that returns an error use +%% this and that they all expose a format_error/1 message that returns +%% an iolist. +-define(RCL_ERROR(Reason), + {error, {?MODULE, Reason}}). diff --git a/relcool.config b/relcool.config deleted file mode 100644 index a86ab3f..0000000 --- a/relcool.config +++ /dev/null @@ -1,3 +0,0 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- -{release, {relcool, "0.0.1"}, - [relcool]}. diff --git a/relx.config b/relx.config new file mode 100644 index 0000000..a86ab3f --- /dev/null +++ b/relx.config @@ -0,0 +1,3 @@ +%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +{release, {relcool, "0.0.1"}, + [relcool]}. diff --git a/src/rcl_app_discovery.erl b/src/rcl_app_discovery.erl deleted file mode 100644 index 7b04b19..0000000 --- a/src/rcl_app_discovery.erl +++ /dev/null @@ -1,207 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provider uses the lib_dir setting of the state. It searches the -%%% Lib Dirs looking for all OTP Applications that are available. When it finds -%%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_app_discovery). - --export([do/2, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t(), [filename:name()]) -> {ok, [rcl_app_info:t()]} | relcool:error(). -do(State, LibDirs) -> - rcl_log:info(rcl_state:log(State), - fun() -> - ["Resolving OTP Applications from directories:\n", - [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] - end), - resolve_app_metadata(State, LibDirs). - --spec format_error([ErrorDetail::term()]) -> iolist(). -format_error(ErrorDetails) - when erlang:is_list(ErrorDetails) -> - [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== -resolve_app_metadata(State, LibDirs) -> - AppMeta0 = lists:flatten(rcl_dscv_util:do(fun discover_dir/2, LibDirs)), - case [case Err of - {error, Ret} -> - Ret - end - || Err <- AppMeta0, - case Err of - {error, _} -> - true; - _ -> - false - end] of - [] -> - SkipApps = rcl_state:skip_apps(State), - AppMeta1 = [App || {ok, App} <- setup_overrides(State, AppMeta0), - not lists:keymember(rcl_app_info:name(App), 1, SkipApps)], - rcl_log:debug(rcl_state:log(State), - fun() -> - ["Resolved the following OTP Applications from the system: \n", - [[rcl_app_info:format(1, App), "\n"] || App <- AppMeta1]] - end), - {ok, AppMeta1}; - Errors -> - ?RCL_ERROR(Errors) - end. - -app_name({error, _}) -> - undefined; -app_name({ok, AppMeta}) -> - rcl_app_info:name(AppMeta). - -setup_overrides(State, AppMetas0) -> - Overrides = rcl_state:overrides(State), - AppMetas1 = [AppMeta || AppMeta <- AppMetas0, - not lists:keymember(app_name(AppMeta), 1, Overrides)], - [case is_valid_otp_app(filename:join([FileName, <<"ebin">>, - erlang:atom_to_list(AppName) ++ ".app"])) of - {noresult, false} -> - {error, {invalid_override, AppName, FileName}}; - Error = {error, _} -> - Error; - {ok, App} -> - {ok, rcl_app_info:link(App, true)} - end || {AppName, FileName} <- Overrides] ++ AppMetas1. - - --spec format_detail(ErrorDetail::term()) -> iolist(). -format_detail({error, {invalid_override, AppName, FileName}}) -> - io_lib:format("Override {~p, ~p} is not a valid OTP App. Perhaps you forgot to build it?", - [AppName, FileName]); -format_detail({accessing, File, eaccess}) -> - io_lib:format("permission denied accessing file ~s", [File]); -format_detail({accessing, File, Type}) -> - io_lib:format("error (~p) accessing file ~s", [Type, File]); -format_detail({no_beam_files, EbinDir}) -> - io_lib:format("no beam files found in directory ~s", [EbinDir]); -format_detail({not_a_directory, EbinDir}) -> - io_lib:format("~s is not a directory when it should be a directory", [EbinDir]); -format_detail({unable_to_load_app, AppDir, _}) -> - io_lib:format("Unable to load the application metadata from ~s", [AppDir]); -format_detail({invalid_app_file, File}) -> - io_lib:format("Application metadata file exists but is malformed: ~s", - [File]); -format_detail({unversioned_app, AppDir, _AppName}) -> - io_lib:format("Application metadata exists but version is not available: ~s", - [AppDir]); -format_detail({app_info_error, {Module, Detail}}) -> - Module:format_error(Detail). - --spec discover_dir([file:name()], directory | file) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. -discover_dir(_File, directory) -> - {noresult, true}; -discover_dir(File, file) -> - is_valid_otp_app(File). - --spec is_valid_otp_app(file:name()) -> {ok, rcl_app_info:t()} | {error, Reason::term()} | - {noresult, false}. - -is_valid_otp_app(File) -> - %% Is this an ebin dir? - EbinDir = filename:dirname(File), - case filename:basename(EbinDir) of - <<"ebin">> -> - case filename:extension(File) of - <<".app">> -> - has_at_least_one_beam(EbinDir, File); - _ -> - {noresult, false} - end; - _ -> - {noresult, false} - end. - --spec has_at_least_one_beam(file:name(), file:filename()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. -has_at_least_one_beam(EbinDir, File) -> - case file:list_dir(EbinDir) of - {ok, List} -> - case lists:any(fun(NFile) -> lists:suffix(".beam", NFile) end, List) of - true -> - gather_application_info(EbinDir, File); - false -> - {error, {no_beam_files, EbinDir}} - end; - _ -> - {error, {not_a_directory, EbinDir}} - end. - --spec gather_application_info(file:name(), file:filename()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. -gather_application_info(EbinDir, File) -> - AppDir = filename:dirname(EbinDir), - case file:consult(File) of - {ok, [{application, AppName, AppDetail}]} -> - get_vsn(AppDir, AppName, AppDetail); - {error, Reason} -> - {error, {unable_to_load_app, AppDir, Reason}}; - _ -> - {error, {invalid_app_file, File}} - end. - --spec get_vsn(file:name(), atom(), proplists:proplist()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. -get_vsn(AppDir, AppName, AppDetail) -> - case proplists:get_value(vsn, AppDetail) of - undefined -> - {error, {unversioned_app, AppDir, AppName}}; - AppVsn -> - case get_deps(AppDir, AppName, AppVsn, AppDetail) of - {ok, App} -> - {ok, App}; - {error, Detail} -> - {error, {app_info_error, Detail}} - end - end. - --spec get_deps(file:name(), atom(), string(), proplists:proplist()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. -get_deps(AppDir, AppName, AppVsn, AppDetail) -> - ActiveApps = proplists:get_value(applications, AppDetail, []), - LibraryApps = proplists:get_value(included_applications, AppDetail, []), - rcl_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps). - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - --endif. diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl deleted file mode 100644 index dadc579..0000000 --- a/src/rcl_app_info.erl +++ /dev/null @@ -1,193 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This module represents useful, relevant information about an -%%% application. The relevant information is. -%%% -%%%
    -%%%
  • Name - The application name as an atom
  • -%%%
  • Vsn - The application version as a list
  • -%%%
  • The root directory of the application. The directory that contains the -%%% ebin/src/priv etc
  • -%%%
  • Active Deps - The Active or 'application' dependencies of the OTP -%%% App. That is the things in the 'applications' property of the application -%%% metadata
  • -%%%
  • Library Deps - The Inactive or Library dependencies of the ATP -%%% app. That is the things in the 'included_applications property of the -%%% application metadata. -%%%
-%%% --module(rcl_app_info). - --export([new/0, - new/5, - name/1, - name/2, - vsn/1, - vsn/2, - vsn_as_string/1, - dir/1, - dir/2, - active_deps/1, - active_deps/2, - library_deps/1, - library_deps/2, - link/1, - link/2, - format_error/1, - format/2, - format/1]). - --export_type([t/0]). - --include_lib("relcool/include/relcool.hrl"). - --record(app_info_t, {name :: atom(), - vsn :: ec_semver:semver(), - dir :: file:name(), - link=false :: boolean(), - active_deps=[]:: [atom()], - library_deps=[] :: [atom()]}). - -%%============================================================================ -%% types -%%============================================================================ --opaque t() :: record(app_info_t). - -%%============================================================================ -%% API -%% ============================================================================ -%% @doc Build a new, empty, app info value. This is not of a lot of use and you -%% probably wont be doing this much. --spec new() -> {ok, t()}. -new() -> - {ok, #app_info_t{}}. - -%% @doc build a complete version of the app info with all fields set. --spec new(atom(), string(), file:name(), [atom()], [atom()]) -> - {ok, t()} | relcool:error(). -new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) -> - new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, false). - -%% @doc build a complete version of the app info with all fields set. --spec new(atom(), string(), file:name(), [atom()], [atom()], boolean()) -> - {ok, t()} | relcool:error(). -new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, Link) - when erlang:is_atom(AppName), - erlang:is_list(ActiveDeps), - erlang:is_list(LibraryDeps) -> - case parse_version(Vsn) of - {fail, _} -> - ?RCL_ERROR({vsn_parse, AppName}); - ParsedVsn -> - {ok, #app_info_t{name=AppName, vsn=ParsedVsn, dir=Dir, - active_deps=ActiveDeps, - library_deps=LibraryDeps, - link=Link}} - end. - --spec name(t()) -> atom(). -name(#app_info_t{name=Name}) -> - Name. - --spec name(t(), atom()) -> t(). -name(AppInfo=#app_info_t{}, AppName) - when erlang:is_atom(AppName) -> - AppInfo#app_info_t{name=AppName}. - --spec vsn(t()) -> ec_semver:semver(). -vsn(#app_info_t{vsn=Vsn}) -> - Vsn. - --spec vsn_as_string(t()) -> string(). -vsn_as_string(#app_info_t{vsn=Vsn}) -> - erlang:binary_to_list(erlang:iolist_to_binary(ec_semver:format(Vsn))). - --spec vsn(t(), string()) -> {ok, t()} | relcool:error(). -vsn(AppInfo=#app_info_t{name=AppName}, AppVsn) - when erlang:is_list(AppVsn) -> - case parse_version(AppVsn) of - {fail, _} -> - ?RCL_ERROR({vsn_parse, AppName}); - ParsedVsn -> - {ok, AppInfo#app_info_t{vsn=ParsedVsn}} - end. - --spec dir(t()) -> file:name(). -dir(#app_info_t{dir=Dir}) -> - Dir. --spec dir(t(), file:name()) -> t(). -dir(AppInfo=#app_info_t{}, Dir) -> - AppInfo#app_info_t{dir=Dir}. - --spec active_deps(t()) -> [atom()]. -active_deps(#app_info_t{active_deps=Deps}) -> - Deps. --spec active_deps(t(), [atom()]) -> t(). -active_deps(AppInfo=#app_info_t{}, ActiveDeps) - when erlang:is_list(ActiveDeps) -> - AppInfo#app_info_t{active_deps=ActiveDeps}. - --spec library_deps(t()) -> [atom()]. -library_deps(#app_info_t{library_deps=Deps}) -> - Deps. - --spec library_deps(t(), [atom()]) -> t(). -library_deps(AppInfo=#app_info_t{}, LibraryDeps) - when erlang:is_list(LibraryDeps) -> - AppInfo#app_info_t{library_deps=LibraryDeps}. - --spec link(t()) -> boolean(). -link(#app_info_t{link=Link}) -> - Link. - --spec link(t(), boolean()) -> t(). -link(AppInfo, NewLink) -> - AppInfo#app_info_t{link=NewLink}. - --spec format_error(Reason::term()) -> iolist(). -format_error({vsn_parse, AppName}) -> - io_lib:format("Error parsing version for ~p", - [AppName]). - --spec format(t()) -> iolist(). -format(AppInfo) -> - format(0, AppInfo). - --spec format(non_neg_integer(), t()) -> iolist(). -format(Indent, #app_info_t{name=Name, vsn=Vsn, dir=Dir, - active_deps=Deps, library_deps=LibDeps, - link=Link}) -> - [rcl_util:indent(Indent), erlang:atom_to_list(Name), "-", ec_semver:format(Vsn), - ": ", Dir, "\n", - rcl_util:indent(Indent + 1), "Symlink: ", erlang:atom_to_list(Link), "\n", - rcl_util:indent(Indent + 1), "Active Dependencies:\n", - [[rcl_util:indent(Indent + 2), erlang:atom_to_list(Dep), ",\n"] || Dep <- Deps], - rcl_util:indent(Indent + 1), "Library Dependencies:\n", - [[rcl_util:indent(Indent + 2), erlang:atom_to_list(LibDep), ",\n"] || LibDep <- LibDeps]]. - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== -parse_version(Vsn) - when erlang:is_list(Vsn) -> - ec_semver:parse(Vsn); -parse_version(Vsn = {_, {_, _}}) -> - Vsn. diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl deleted file mode 100644 index fee4449..0000000 --- a/src/rcl_cmd_args.erl +++ /dev/null @@ -1,281 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc Trivial utility file to help handle common tasks --module(rcl_cmd_args). - --export([args2state/2, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ --spec args2state([getopt:option()], [string()]) -> - {ok, {rcl_state:t(), [string()]}} | - relcool:error(). -args2state(Opts, Target) - when erlang:length(Target) == 0; erlang:length(Target) == 1 -> - RelName = proplists:get_value(relname, Opts, undefined), - RelVsn = proplists:get_value(relvsn, Opts, undefined), - case convert_target(Target) of - {ok, AtomizedTarget} -> - case create_log(Opts, [{relname, RelName}, - {relvsn, RelVsn}]) of - Error = {error, _} -> - Error; - {ok, CommandLineConfig} -> - handle_config(Opts, AtomizedTarget, CommandLineConfig) - end; - Error -> - Error - end; -args2state(_Opts, Targets) -> - ?RCL_ERROR({invalid_targets, Targets}). - --spec format_error(Reason::term()) -> iolist(). -format_error({invalid_targets, Targets}) -> - io_lib:format("One config must be specified! not ~p~n", [Targets]); -format_error({invalid_option_arg, Arg}) -> - case Arg of - {goals, Goal} -> - io_lib:format("Invalid Goal argument -g ~p~n", [Goal]); - {relname, RelName} -> - io_lib:format("Invalid Release Name argument -n ~p~n", [RelName]); - {relvsn, RelVsn} -> - io_lib:format("Invalid Release Version argument -n ~p~n", [RelVsn]); - {output_dir, Outdir} -> - io_lib:format("Invalid Output Directory argument -n ~p~n", [Outdir]); - {lib_dir, LibDir} -> - io_lib:format("Invalid Library Directory argument -n ~p~n", [LibDir]); - {log_level, LogLevel} -> - io_lib:format("Invalid Library Directory argument -n ~p~n", [LogLevel]) - end; -format_error({invalid_config_file, Config}) -> - io_lib:format("Invalid configuration file specified: ~s", [Config]); -format_error({invalid_caller, Caller}) -> - io_lib:format("Invalid caller specified: ~s", [Caller]); -format_error({failed_to_parse, Spec}) -> - io_lib:format("Unable to parse spec ~s", [Spec]); -format_error({failed_to_parse_override, QA}) -> - io_lib:format("Failed to parse app override ~s", [QA]); -format_error({not_directory, Dir}) -> - io_lib:format("Library directory does not exist: ~s", [Dir]); -format_error({invalid_log_level, LogLevel}) -> - io_lib:format("Invalid log level specified -V ~p, log level must be in the" - " range 0..2", [LogLevel]); -format_error({invalid_target, Target}) -> - io_lib:format("Invalid action specified: ~s", [Target]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec handle_config([getopt:option()], atom(), proplists:proplist()) -> - {ok, {rcl_state:t(), [string()]}} | - relcool:error(). -handle_config(Opts, Target, CommandLineConfig) -> - case validate_config(proplists:get_value(config, Opts, [])) of - Error = {error, _} -> - Error; - {ok, Config} -> - {ok, rcl_state:new([{config, Config} | CommandLineConfig], Target)} - end. - --spec convert_target([string()]) -> {ok, release | relup} | relcool:error(). -convert_target([]) -> - {ok, release}; -convert_target(["release"]) -> - {ok, release}; -convert_target(["relup"]) -> - {ok, relup}; -convert_target(Target) -> - ?RCL_ERROR({invalid_target, Target}). - --spec validate_config(file:filename() | undefined) -> - {ok, file:filename() | undefined} | relcool:error(). -validate_config(undefined) -> - {ok, undefined}; -validate_config("") -> - {ok, undefined}; -validate_config(Config) -> - case filelib:is_regular(Config) of - true -> - {ok, filename:absname(Config)}; - false -> - ?RCL_ERROR({invalid_config_file, Config}) - end. - --spec create_log([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_log(Opts, Acc) -> - LogLevel = proplists:get_value(log_level, Opts, 0), - if - LogLevel >= 0, LogLevel =< 2 -> - create_goals(Opts, [{log, rcl_log:new(LogLevel)} | Acc]); - true -> - ?RCL_ERROR({invalid_log_level, LogLevel}) - end. - --spec create_goals([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_goals(Opts, Acc) -> - Goals = proplists:get_value(goals, Opts, []) ++ - proplists:get_all_values(goal, Opts), - case convert_goals(Goals, []) of - Error={error, _} -> - Error; - {ok, Specs} -> - create_overrides(Opts, [{goals, Specs} | Acc]) - end. - --spec create_overrides([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_overrides(Opts, Acc) -> - Overrides = proplists:get_all_values(override, Opts) ++ - proplists:get_value(overrides, Opts, []), - case convert_overrides(Overrides, []) of - {ok, Overrides} -> - create_output_dir(Opts, [{overrides, Overrides} | Acc]); - Error -> - Error - end. - --spec convert_overrides([{atom(), string() | binary()} | - string() | binary()], [{atom(), string() | binary()}]) -> - {ok, [string() | binary()]} | relcool:error(). -convert_overrides([], Acc) -> - {ok, Acc}; -convert_overrides([QA = {OverrideApp, _} | Rest], Acc) - when erlang:is_atom(OverrideApp) -> - convert_overrides(Rest, [QA | Acc]); -convert_overrides([Override | Rest], Acc) - when erlang:is_list(Override); erlang:is_binary(Override) -> - case re:split(Override, ":") of - [AppName, AppDir] -> - convert_overrides(Rest, [{erlang:iolist_to_binary(AppName), AppDir} | Acc]); - _ -> - ?RCL_ERROR({failed_to_parse_override, Override}) - end; -convert_overrides([QA | _], _) -> - ?RCL_ERROR({failed_to_parse_override, QA}). - --spec convert_goals([string()], [rcl_depsolver:constraint()]) -> - {ok,[rcl_depsolver:constraint()]} | - relcool:error(). -convert_goals([], Specs) -> - %% Reverse the specs because order matters to rcl_depsolver - {ok, lists:reverse(Specs)}; -convert_goals([RawSpec | Rest], Acc) -> - parse_goal(RawSpec, Rest, Acc). - --spec parse_goal(string() | binary() | rcl_depsolver:constraint(), - [string() | binary() | rcl_depsolver:constraint()], - rcl_depsolver:constraints()) -> - {ok, rcl_depsolver:constraints()} | relcool:error(). -parse_goal(Spec, Rest, Acc) - when erlang:is_atom(Spec) -> - convert_goals(Rest, [Spec | Acc]); -parse_goal(Spec, Rest, Acc) - when erlang:is_tuple(Spec) -> - convert_goals(Rest, [Spec | Acc]); -parse_goal(RawSpec, Rest, Acc) -> - case rcl_goal:parse(RawSpec) of - {ok, Spec} -> - convert_goals(Rest, [Spec | Acc]); - {fail, _} -> - ?RCL_ERROR({failed_to_parse, RawSpec}) - end. - --spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_output_dir(Opts, Acc) -> - OutputDir = proplists:get_value(output_dir, Opts, "./_rel"), - create_lib_dirs(Opts, [{output_dir, filename:absname(OutputDir)} | Acc]). - --spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_lib_dirs(Opts, Acc) -> - Dirs = proplists:get_all_values(lib_dir, Opts) ++ - proplists:get_value(lib_dirs, Opts, []), - case check_lib_dirs(Dirs) of - Error = {error, _} -> - Error; - ok -> - create_root_dir(Opts, [{lib_dirs, [filename:absname(Dir) || Dir <- Dirs]} | Acc]) - end. - --spec create_root_dir([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_root_dir(Opts, Acc) -> - Dir = proplists:get_value(root_dir, Opts, undefined), - case Dir of - undefined -> - {ok, Cwd} = file:get_cwd(), - create_disable_default_libs(Opts, [{root_dir, Cwd} | Acc]); - _ -> - create_disable_default_libs(Opts, [{root_dir, Dir} | Acc]) - end. - --spec create_disable_default_libs([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_disable_default_libs(Opts, Acc) -> - Def = proplists:get_value(disable_default_libs, Opts, false), - create_upfrom(Opts, [{disable_default_libs, Def} | Acc]). - --spec create_upfrom([getopt:option()], rcl:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_upfrom(Opts, Acc) -> - case proplists:get_value(upfrom, Opts, undefined) of - undefined -> - create_caller(Opts, Acc); - UpFrom -> - create_caller(Opts, [{upfrom, UpFrom} | Acc]) - end. - --spec create_caller([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). -create_caller(Opts, Acc) -> - case proplists:get_value(caller, Opts, api) of - "command_line" -> - {ok, [{caller, command_line} | Acc]}; - "commandline" -> - {ok, [{caller, command_line} | Acc]}; - "api" -> - {ok, [{caller, api} | Acc]}; - api -> - {ok, [{caller, api} | Acc]}; - commandline -> - {ok, [{caller, command_line} | Acc]}; - command_line -> - {ok, [{caller, command_line} | Acc]}; - Caller -> - ?RCL_ERROR({invalid_caller, Caller}) - end. --spec check_lib_dirs([string()]) -> ok | relcool:error(). -check_lib_dirs([]) -> - ok; -check_lib_dirs([Dir | Rest]) -> - case filelib:is_dir(Dir) of - false -> - ?RCL_ERROR({not_directory, Dir}); - true -> - check_lib_dirs(Rest) - end. diff --git a/src/rcl_depsolver.erl b/src/rcl_depsolver.erl deleted file mode 100644 index 0cf8c88..0000000 --- a/src/rcl_depsolver.erl +++ /dev/null @@ -1,718 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%% ex: ts=4 sx=4 et -%% -%% Copyright 2012 Opscode, Inc. All Rights Reserved. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% -%% @author Eric Merritt -%% -%%%------------------------------------------------------------------- -%%% @doc -%%% This is a dependency constraint solver. You add your 'world' to the -%%% solver. That is the packages that exist, their versions and their -%%% dependencies. Then give the system a set of targets and ask it to solve. -%%% -%%% Lets say our world looks as follows -%%% -%%% app1 that has versions "0.1" -%%% depends on app3 any version greater then "0.2" -%%% "0.2" with no dependencies -%%% "0.3" with no dependencies -%%% -%%% app2 that has versions "0.1" with no dependencies -%%% "0.2" that depends on app3 exactly "0.3" -%%% "0.3" with no dependencies -%%% -%%% app3 that has versions -%%% "0.1", "0.2" and "0.3" all with no dependencies -%%% -%%% we can add this world to the system all at once as follows -%%% -%%% Graph0 = rcl_depsolver:new_graph(), -%%% Graph1 = rcl_depsolver:add_packages( -%%% [{app1, [{"0.1", [{app2, "0.2"}, -%%% {app3, "0.2", '>='}]}, -%%% {"0.2", []}, -%%% {"0.3", []}]}, -%%% {app2, [{"0.1", []}, -%%% {"0.2",[{app3, "0.3"}]}, -%%% {"0.3", []}]}, -%%% {app3, [{"0.1", []}, -%%% {"0.2", []}, -%%% {"0.3", []}]}]). -%%% -%%% We can also build it up incrementally using the other add_package and -%%% add_package_version functions. -%%% -%%% Finally, once we have built up the graph we can ask rcl_depsolver to solve the -%%% dependency constraints. That is to give us a list of valid dependencies by -%%% using the solve function. Lets say we want the app3 version "0.3" and all of -%%% its resolved dependencies. We could call solve as follows. -%%% -%%% rcl_depsolver:solve(Graph1, [{app3, "0.3"}]). -%%% -%%% That will give us the completely resolved dependencies including app3 -%%% itself. Lets be a little more flexible. Lets ask for a graph that is rooted -%%% in anything greater then or equal to app3 "0.3". We could do that by -%%% -%%% rcl_depsolver:solve(Graph1, [{app3, "0.3", '>='}]). -%%% -%%% Of course, you can specify any number of goals at the top level. -%%% @end -%%%------------------------------------------------------------------- --module(rcl_depsolver). - -%% Public Api --export([format_error/1, - format_roots/1, - format_culprits/1, - format_constraint/1, - format_version/1, - new_graph/0, - solve/2, - add_packages/2, - add_package/3, - add_package_version/3, - add_package_version/4, - parse_version/1, - is_valid_constraint/1, - filter_packages/2]). - -%% Internally Exported API. This should *not* be used outside of the rcl_depsolver -%% application. You have been warned. --export([dep_pkg/1, - filter_package/2, - primitive_solve/3]). - --export_type([t/0, - pkg/0, - constraint_op/0, - pkg_name/0, - vsn/0, - constraint/0, - dependency_set/0]). - --export_type([dep_graph/0, constraints/0, - ordered_constraints/0, fail_info/0, - fail_detail/0]). -%%============================================================================ -%% type -%%============================================================================ --type dep_graph() :: gb_tree(). --opaque t() :: {?MODULE, dep_graph()}. --type pkg() :: {pkg_name(), vsn()}. --type pkg_name() :: binary() | atom(). --type raw_vsn() :: ec_semver:any_version(). - --type vsn() :: 'NO_VSN' - | ec_semver:semver(). - --type constraint_op() :: - '=' | gte | '>=' | lte | '<=' - | gt | '>' | lt | '<' | pes | '~>' | between. - --type raw_constraint() :: pkg_name() - | {pkg_name(), raw_vsn()} - | {pkg_name(), raw_vsn(), constraint_op()} - | {pkg_name(), raw_vsn(), vsn(), between}. - --type constraint() :: pkg_name() - | {pkg_name(), vsn()} - | {pkg_name(), vsn(), constraint_op()} - | {pkg_name(), vsn(), vsn(), between}. - - --type vsn_constraint() :: {raw_vsn(), [raw_constraint()]}. --type dependency_set() :: {pkg_name(), [vsn_constraint()]}. - -%% Internal Types --type constraints() :: [constraint()]. --type ordered_constraints() :: [{pkg_name(), constraints()}]. --type fail_info() :: {[pkg()], ordered_constraints()}. --type fail_detail() :: {fail, [fail_info()]}. --type version_checker() :: fun((vsn()) -> fail_detail() | vsn()). - -%%============================================================================ -%% API -%%============================================================================ -%% @doc create a new empty dependency graph --spec new_graph() -> t(). -new_graph() -> - {?MODULE, gb_trees:empty()}. - -%% @doc add a complete set of list of packages to the graph. Where the package -%% consists of the name and a list of versions and dependencies. -%% -%% ``` rcl_depsolver:add_packages(Graph, -%% [{app1, [{"0.1", [{app2, "0.2"}, -%% {app3, "0.2", '>='}]}, -%% {"0.2", []}, -%% {"0.3", []}]}, -%% {app2, [{"0.1", []}, -%% {"0.2",[{app3, "0.3"}]}, -%% {"0.3", []}]}, -%% {app3, [{"0.1", []}, -%% {"0.2", []}, -%% {"0.3", []}]}]) -%% ''' --spec add_packages(t(),[dependency_set()]) -> t(). -add_packages(Dom0, Info) - when is_list(Info) -> - lists:foldl(fun({Pkg, VsnInfo}, Dom1) -> - add_package(Dom1, Pkg, VsnInfo) - end, Dom0, Info). - -%% @doc add a single package to the graph, where it consists of a package name -%% and its versions and thier dependencies. -%% ```rcl_depsolver:add_package(Graph, app1, [{"0.1", [{app2, "0.2"}, -%% {app3, "0.2", '>='}]}, -%% {"0.2", []}, -%% {"0.3", []}]}]). -%% ''' --spec add_package(t(),pkg_name(),[vsn_constraint()]) -> t(). -add_package(State, Pkg, Versions) - when is_list(Versions) -> - lists:foldl(fun({Vsn, Constraints}, Dom1) -> - add_package_version(Dom1, Pkg, Vsn, Constraints); - (Version, Dom1) -> - add_package_version(Dom1, Pkg, Version, []) - end, State, Versions). - -%% @doc add a set of dependencies to a specific package and version. -%% and its versions and thier dependencies. -%% ```rcl_depsolver:add_package(Graph, app1, "0.1", [{app2, "0.2"}, -%% {app3, "0.2", '>='}]}, -%% {"0.2", []}, -%% {"0.3", []}]). -%% ''' --spec add_package_version(t(), pkg_name(), raw_vsn(), [raw_constraint()]) -> t(). -add_package_version({?MODULE, Dom0}, RawPkg, RawVsn, RawPkgConstraints) -> - Pkg = fix_pkg(RawPkg), - Vsn = parse_version(RawVsn), - %% Incoming constraints are raw - %% and need to be fixed - PkgConstraints = [fix_con(PkgConstraint) || - PkgConstraint <- RawPkgConstraints], - Info2 = - case gb_trees:lookup(Pkg, Dom0) of - {value, Info0} -> - case lists:keytake(Vsn, 1, Info0) of - {value, {Vsn, Constraints}, Info1} -> - [{Vsn, join_constraints(Constraints, - PkgConstraints)} - | Info1]; - false -> - [{Vsn, PkgConstraints} | Info0] - end; - none -> - [{Vsn, PkgConstraints}] - end, - {?MODULE, gb_trees:enter(Pkg, Info2, Dom0)}. - -%% @doc add a package and version to the dependency graph with no dependency -%% constraints, dependency constraints can always be added after the fact. -%% -%% ```rcl_depsolver:add_package_version(Graph, app1, "0.1").''' --spec add_package_version(t(),pkg_name(),raw_vsn()) -> t(). -add_package_version(State, Pkg, Vsn) -> - add_package_version(State, Pkg, Vsn, []). - -%% @doc Given a set of goals (in the form of constrains) find a set of packages -%% and versions that satisfy all constraints. If no solution can be found then -%% an exception is thrown. -%% ``` rcl_depsolver:solve(State, [{app1, "0.1", '>='}]).''' --spec solve(t(),[constraint()]) -> {ok, [pkg()]} | {error, term()}. -solve({?MODULE, DepGraph0}, RawGoals) - when erlang:length(RawGoals) > 0 -> - Goals = [fix_con(Goal) || Goal <- RawGoals], - case trim_unreachable_packages(DepGraph0, Goals) of - Error = {error, _} -> - Error; - DepGraph1 -> - case primitive_solve(DepGraph1, Goals, no_path) of - {fail, _} -> - [FirstCons | Rest] = Goals, - {error, rcl_depsolver_culprit:search(DepGraph1, [FirstCons], Rest)}; - Solution -> - {ok, Solution} - end - end. - -%% @doc Parse a string version into a tuple based version --spec parse_version(raw_vsn() | vsn()) -> vsn(). -parse_version(RawVsn) - when erlang:is_list(RawVsn); - erlang:is_binary(RawVsn) -> - ec_semver:parse(RawVsn); -parse_version(Vsn) - when erlang:is_tuple(Vsn) -> - Vsn. - -%% @doc check that a specified constraint is a valid constraint. --spec is_valid_constraint(constraint()) -> boolean(). -is_valid_constraint(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> - true; -is_valid_constraint({_Pkg, Vsn}) when is_tuple(Vsn) -> - true; -is_valid_constraint({_Pkg, Vsn, '='}) when is_tuple(Vsn) -> - true; -is_valid_constraint({_Pkg, _LVsn, gte}) -> - true; -is_valid_constraint({_Pkg, _LVsn, '>='}) -> - true; -is_valid_constraint({_Pkg, _LVsn, lte}) -> - true; -is_valid_constraint({_Pkg, _LVsn, '<='}) -> - true; -is_valid_constraint({_Pkg, _LVsn, gt}) -> - true; -is_valid_constraint({_Pkg, _LVsn, '>'}) -> - true; -is_valid_constraint({_Pkg, _LVsn, lt}) -> - true; -is_valid_constraint({_Pkg, _LVsn, '<'}) -> - true; -is_valid_constraint({_Pkg, _LVsn, pes}) -> - true; -is_valid_constraint({_Pkg, _LVsn, '~>'}) -> - true; -is_valid_constraint({_Pkg, _LVsn1, _LVsn2, between}) -> - true; -is_valid_constraint(_InvalidConstraint) -> - false. - - -%% @doc given a list of package name version pairs, and a list of constraints -%% return every member of that list that matches all constraints. --spec filter_packages([{pkg_name(), raw_vsn()}], [raw_constraint()]) -> - {ok, [{pkg_name(), raw_vsn()}]} - | {error, Reason::term()}. -filter_packages(PVPairs, RawConstraints) -> - Constraints = [fix_con(Constraint) || Constraint <- RawConstraints], - case check_constraints(Constraints) of - ok -> - {ok, [PVPair || PVPair <- PVPairs, - filter_pvpair_by_constraint(fix_con(PVPair), Constraints)]}; - Error -> - Error - end. - -%% @doc Produce a full error message for a given error condition. This includes -%% details of the paths taken to resolve the dependencies and shows which dependencies -%% could not be satisfied --spec format_error({error, {unreachable_package, list()} | - {invalid_constraints, [constraint()]} | - list()}) -> iolist(). -format_error(Error) -> - rcl_depsolver_culprit:format_error(Error). - -%% @doc Return a formatted list of roots of the dependency trees which -%% could not be satisified. These may also have versions attached. -%% Example: -%% -%% ```(foo = 1.2.0), bar``` -%% --spec format_roots([constraints()]) -> iolist(). -format_roots(Roots) -> - rcl_depsolver_culprit:format_roots(Roots). - - -%% @doc Return a formatted list of the culprit depenedencies which led to -%% the dependencies not being satisfied. Example: -%% -%% ```(foo = 1.2.0) -> (bar > 2.0.0)``` --spec format_culprits([{[constraint()], [constraint()]}]) -> iolist(). -format_culprits(Culprits) -> - rcl_depsolver_culprit:format_culprits(Culprits). - -%% @doc A formatted version tuple --spec format_version(vsn()) -> iolist(). -format_version(Version) -> - rcl_depsolver_culprit:format_version(Version). - -%% @doc A formatted constraint tuple --spec format_constraint(constraint()) -> iolist(). -format_constraint(Constraint) -> - rcl_depsolver_culprit:format_constraint(Constraint). - -%%==================================================================== -%% Internal Functions -%%==================================================================== --spec check_constraints(constraints()) -> - ok | {error, {invalid_constraints, [term()]}}. -check_constraints(Constraints) -> - PossibleInvalids = - lists:foldl(fun(Constraint, InvalidConstraints) -> - case is_valid_constraint(Constraint) of - true -> - InvalidConstraints; - false -> - [Constraint | InvalidConstraints] - end - end, [], Constraints), - case PossibleInvalids of - [] -> - ok; - _ -> - {error, {invalid_constraints, PossibleInvalids}} - end. - - --spec filter_pvpair_by_constraint({pkg_name(), vsn()}, [constraint()]) -> - boolean(). -filter_pvpair_by_constraint(PVPair, Constraints) -> - lists:all(fun(Constraint) -> - filter_package(PVPair, Constraint) - end, Constraints). - --spec filter_package({pkg_name(), vsn()}, constraint()) -> - boolean(). -filter_package({PkgName, Vsn}, C = {PkgName, _}) -> - is_version_within_constraint(Vsn, C); -filter_package({PkgName, Vsn}, C = {PkgName, _, _}) -> - is_version_within_constraint(Vsn, C); -filter_package({PkgName, Vsn}, C = {PkgName, _, _, _}) -> - is_version_within_constraint(Vsn, C); -filter_package(_, _) -> - %% If its not explicitly excluded its included - true. - -%% @doc -%% fix the package name. If its a list turn it into a binary otherwise leave it as an atom -fix_pkg(Pkg) when is_list(Pkg) -> - erlang:list_to_binary(Pkg); -fix_pkg(Pkg) when is_binary(Pkg); is_atom(Pkg) -> - Pkg. - -%% @doc -%% fix package. Take a package with a possible invalid version and fix it. --spec fix_con(raw_constraint()) -> constraint(). -fix_con({Pkg, Vsn}) -> - {fix_pkg(Pkg), parse_version(Vsn)}; -fix_con({Pkg, Vsn, CI}) -> - {fix_pkg(Pkg), parse_version(Vsn), CI}; -fix_con({Pkg, Vsn1, Vsn2, CI}) -> - {fix_pkg(Pkg), parse_version(Vsn1), - parse_version(Vsn2), CI}; -fix_con(Pkg) -> - fix_pkg(Pkg). - - -%% @doc given two lists of constraints join them in such a way that no -%% constraint is duplicated but the over all order of the constraints is -%% preserved. Order drives priority in this solver and is important for that -%% reason. --spec join_constraints([constraint()], [constraint()]) -> - [constraint()]. -join_constraints(NewConstraints, ExistingConstraints) -> - ECSet = sets:from_list(ExistingConstraints), - FilteredNewConstraints = [NC || NC <- NewConstraints, - not sets:is_element(NC, ECSet)], - ExistingConstraints ++ FilteredNewConstraints. - - -%% @doc constraints is an associated list keeping track of all the constraints -%% that have been placed on a package --spec new_constraints() -> constraints(). -new_constraints() -> - []. - -%% @doc Given a dep graph and a set of goals this either solves the problem or -%% fails. This is basically the root solver of the system, the main difference -%% from the exported solve/2 function is the fact that this does not do the -%% culprit search. --spec primitive_solve(dep_graph(),[constraint()], term()) -> - [pkg()] | fail_detail(). -primitive_solve(State, PackageList, PathInd) - when erlang:length(PackageList) > 0 -> - Constraints = lists:foldl(fun(Info, Acc) -> - add_constraint('_GOAL_', 'NO_VSN', Acc, Info) - end, new_constraints(), PackageList), - - Pkgs = lists:map(fun dep_pkg/1, PackageList), - all_pkgs(State, [], Pkgs, Constraints, PathInd). - -%% @doc -%% given a Pkg | {Pkg, Vsn} | {Pkg, Vsn, Constraint} return Pkg --spec dep_pkg(constraint()) -> pkg_name(). -dep_pkg({Pkg, _Vsn}) -> - Pkg; -dep_pkg({Pkg, _Vsn, _}) -> - Pkg; -dep_pkg({Pkg, _Vsn1, _Vsn2, _}) -> - Pkg; -dep_pkg(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> - Pkg. - --spec add_constraint(pkg_name(), vsn(), [constraint()],constraint()) -> ordered_constraints(). -add_constraint(SrcPkg, SrcVsn, PkgsConstraints, PkgConstraint) -> - case is_valid_constraint(PkgConstraint) of - true -> ok; - false -> erlang:throw({invalid_constraint, PkgConstraint}) - end, - PkgName = dep_pkg(PkgConstraint), - Constraints1 = - case lists:keysearch(PkgName, 1, PkgsConstraints) of - false -> - []; - {value, {PkgName, Constraints0}} -> - Constraints0 - end, - [{PkgName, [{PkgConstraint, {SrcPkg, SrcVsn}} | Constraints1]} - | lists:keydelete(PkgName, 1, PkgsConstraints)]. - -%% @doc -%% Extend the currently active constraints correctly for the given constraints. --spec extend_constraints(pkg_name(), vsn(), constraints(),constraints()) -> - [{pkg_name(), constraints()}]. -extend_constraints(SrcPkg, SrcVsn, ExistingConstraints0, NewConstraints) -> - lists:foldl(fun (Constraint, ExistingConstraints1) -> - add_constraint(SrcPkg, SrcVsn, ExistingConstraints1, Constraint) - end, - ExistingConstraints0, [{SrcPkg, SrcVsn} | NewConstraints]). - --spec is_version_within_constraint(vsn(),constraint()) -> boolean(). -is_version_within_constraint(_Vsn, Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> - true; -is_version_within_constraint(Vsn, {_Pkg, NVsn}) -> - ec_semver:eql(Vsn, NVsn); -is_version_within_constraint(Vsn, {_Pkg, NVsn, '='}) -> - ec_semver:eql(Vsn, NVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, gte}) -> - ec_semver:gte(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, '>='}) -> - ec_semver:gte(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, lte}) -> - ec_semver:lte(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, '<='}) -> - ec_semver:lte(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, gt}) -> - ec_semver:gt(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, '>'}) -> - ec_semver:gt(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, lt}) -> - ec_semver:lt(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, '<'}) -> - ec_semver:lt(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, 'pes'}) -> - ec_semver:pes(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn, '~>'}) -> - ec_semver:pes(Vsn, LVsn); -is_version_within_constraint(Vsn, {_Pkg, LVsn1, LVsn2, between}) -> - ec_semver:between(LVsn1, LVsn2, Vsn); -is_version_within_constraint(_Vsn, _Pkg) -> - false. - -%% @doc -%% Get the currently active constraints that relate to the specified package --spec get_constraints([{pkg_name(), constraints()}],pkg_name()) -> constraints(). -get_constraints(PkgsConstraints, PkgName) -> - case lists:keysearch(PkgName, 1, PkgsConstraints) of - false -> - []; - {value, {PkgName, Constraints}} -> - Constraints - end. - -%% @doc -%% Given a package name get the list of all versions available for that package. --spec get_versions(dep_graph(),pkg_name()) -> [vsn()]. -get_versions(DepGraph, PkgName) -> - case gb_trees:lookup(PkgName, DepGraph) of - none -> - []; - {value, AllVsns} -> - [Vsn || {Vsn, _} <- AllVsns] - end. - -%% @doc -%% make sure a given name/vsn meets all current constraints --spec valid_version(pkg_name(),vsn(),constraints()) -> boolean(). -valid_version(PkgName, Vsn, PkgConstraints) -> - lists:all(fun ({L, _ConstraintSrc}) -> - is_version_within_constraint(Vsn, L) - end, - get_constraints(PkgConstraints, PkgName)). - -%% @doc -%% Given a Package Name and a set of constraints get a list of package -%% versions that meet all constraints. --spec constrained_package_versions(dep_graph(),pkg_name(),constraints()) -> - [vsn()]. -constrained_package_versions(State, PkgName, PkgConstraints) -> - Versions = get_versions(State, PkgName), - [Vsn || Vsn <- Versions, valid_version(PkgName, Vsn, PkgConstraints)]. - -%% Given a list of constraints filter said list such that only fail (for things -%% that do not match a package and pkg are returned. Since at the end only pkg() -%% we should have a pure list of packages. --spec filter_package_constraints([constraint()]) -> fail | pkg(). -filter_package_constraints([]) -> - fail; -filter_package_constraints([PkgCon | PkgConstraints]) -> - case PkgCon of - {Pkg, _} when is_atom(Pkg); is_binary(Pkg) -> - filter_package_constraints(PkgConstraints); - {{_Pkg1, _Vsn} = PV, _} -> - PV; - {{_Pkg2, _Vsn, _R}, _} -> - filter_package_constraints(PkgConstraints); - {{_Pkg2, _Vsn1, _Vsn2, _R}, _} -> - filter_package_constraints(PkgConstraints) - end. - -%% @doc all_pkgs is one of the set of mutually recursive functions (all_pkgs and -%% pkgs) that serve to walk the solution space of dependency. --spec all_pkgs(dep_graph(),[pkg()],[pkg_name()],[{pkg_name(), constraints()}], term()) -> - fail_detail() | [pkg()]. -all_pkgs(_State, Visited, [], Constraints, _PathInd) -> - PkgVsns = - [filter_package_constraints(PkgConstraints) - || {_, PkgConstraints} <- Constraints], - - %% PkgVsns should be a list of pkg() where all the constraints are correctly - %% met. If not we fail the solution. If so we return those pkg()s - case lists:all(fun({Pkg, Vsn}) -> - lists:all(fun({Constraint, _}) -> - is_version_within_constraint(Vsn, Constraint) - end, get_constraints(Constraints, Pkg)) - end, PkgVsns) of - true -> - PkgVsns; - false -> - {fail, [{Visited, Constraints}]} - end; -all_pkgs(State, Visited, [PkgName | PkgNames], Constraints, PathInd) - when is_atom(PkgName); is_binary(PkgName) -> - case lists:keymember(PkgName, 1, Visited) of - true -> - all_pkgs(State, Visited, PkgNames, Constraints, PathInd); - false -> - pkgs(State, Visited, PkgName, Constraints, PkgNames, PathInd) - end. - -%% @doc this is the key graph walker. Set of constraints it walks forward into -%% the solution space searching for a path that solves all dependencies. --spec pkgs(dep_graph(),[pkg()], pkg_name(), [{pkg_name(), constraints()}], - [pkg_name()], term()) -> fail_detail() | [pkg()]. -pkgs(DepGraph, Visited, Pkg, Constraints, OtherPkgs, PathInd) -> - F = fun (Vsn) -> - Deps = get_dep_constraints(DepGraph, Pkg, Vsn), - UConstraints = extend_constraints(Pkg, Vsn, Constraints, Deps), - DepPkgs =[dep_pkg(Dep) || Dep <- Deps], - NewVisited = [{Pkg, Vsn} | Visited], - Res = all_pkgs(DepGraph, NewVisited, DepPkgs ++ OtherPkgs, UConstraints, PathInd), - Res - - end, - case constrained_package_versions(DepGraph, Pkg, Constraints) of - [] -> - {fail, [{Visited, Constraints}]}; - Res -> - lists_some(F, Res, PathInd) - end. - - -%% @doc This gathers the dependency constraints for a given package vsn from the -%% dependency graph. --spec get_dep_constraints(dep_graph(), pkg_name(), vsn()) -> [constraint()]. -get_dep_constraints(DepGraph, PkgName, Vsn) -> - {Vsn, Constraints} = lists:keyfind(Vsn, 1, - gb_trees:get(PkgName, DepGraph)), - Constraints. - - -lists_some(F, Res, PathInd) -> - lists_some(F, Res, [], PathInd). - --spec lists_some(version_checker(), [vsn()], term(), term()) -> vsn() | fail_detail(). -%% @doc lists_some is the root of the system the actual backtracing search that -%% makes the dep solver posible. It a takes a function that checks whether the -%% 'problem' has been solved and an fail indicator. As long as the evaluator -%% returns the fail indicator processing continues. If the evaluator returns -%% anything but the fail indicator that indicates success. -lists_some(_, [], FailPaths, _PathInd) -> - {fail, FailPaths}; -lists_some(F, [H | T], FailPaths, PathInd) -> - case F(H) of - {fail, FailPath} -> - case PathInd of - keep_paths -> - lists_some(F, T, [FailPath | FailPaths], PathInd); - _ -> - lists_some(F, T, [], PathInd) - end; - Res -> - Res - end. - -%% @doc given a graph and a set of top level goals return a graph that contains -%% only those top level packages and those packages that might be required by -%% those packages. --spec trim_unreachable_packages(dep_graph(), [constraint()]) -> - dep_graph() | {error, term()}. -trim_unreachable_packages(State, Goals) -> - {_, NewState0} = new_graph(), - lists:foldl(fun(_Pkg, Error={error, _}) -> - Error; - (Pkg, NewState1) -> - PkgName = dep_pkg(Pkg), - find_reachable_packages(State, NewState1, PkgName) - end, NewState0, Goals). - -%% @doc given a list of versions and the constraints for that version rewrite -%% the new graph to reflect the requirements of those versions. --spec rewrite_vsns(dep_graph(), dep_graph(), [{vsn(), [constraint()]}]) -> - dep_graph() | {error, term()}. -rewrite_vsns(ExistingGraph, NewGraph0, Info) -> - lists:foldl(fun(_, Error={error, _}) -> - Error; - ({_Vsn, Constraints}, NewGraph1) -> - lists:foldl(fun(_DepPkg, Error={error, _}) -> - Error; - (DepPkg, NewGraph2) -> - DepPkgName = dep_pkg(DepPkg), - find_reachable_packages(ExistingGraph, - NewGraph2, - DepPkgName) - end, NewGraph1, Constraints) - end, NewGraph0, Info). - -%% @doc Rewrite the existing dep graph removing anything that is not reachable -%% required by the goals or any of its potential dependencies. --spec find_reachable_packages(dep_graph(), dep_graph(), pkg_name()) -> - dep_graph() | {error, term()}. -find_reachable_packages(_ExistingGraph, Error={error, _}, _PkgName) -> - Error; -find_reachable_packages(ExistingGraph, NewGraph0, PkgName) -> - case contains_package_version(NewGraph0, PkgName) of - true -> - NewGraph0; - false -> - case gb_trees:lookup(PkgName, ExistingGraph) of - {value, Info} -> - NewGraph1 = gb_trees:insert(PkgName, Info, NewGraph0), - rewrite_vsns(ExistingGraph, NewGraph1, Info); - none -> - {error, {unreachable_package, PkgName}} - end - end. - -%% @doc -%% Checks to see if a package name has been defined in the dependency graph --spec contains_package_version(dep_graph(), pkg_name()) -> boolean(). -contains_package_version(Dom0, PkgName) -> - gb_trees:is_defined(PkgName, Dom0). diff --git a/src/rcl_depsolver_culprit.erl b/src/rcl_depsolver_culprit.erl deleted file mode 100644 index f2115c7..0000000 --- a/src/rcl_depsolver_culprit.erl +++ /dev/null @@ -1,359 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%% ex: ts=4 sx=4 et -%% -%% @author Eric Merritt -%% -%% Copyright 2012 Opscode, Inc. All Rights Reserved. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% -%%%------------------------------------------------------------------- -%%% @doc -%%% This app does the culprit search for a failed solve. It searches -%%% through the goals provided by the user trying to find the first one -%%% that fails. It then returns that as the culprit along with the -%%% unknown apps from the goal, the version constrained apps from the -%%% goal, and the good apps (those not immediately constrained from -%%% the goals). -%%% @end --module(rcl_depsolver_culprit). - --export([search/3, - format_error/1, - format_version/1, - format_constraint/1, - format_roots/1, - format_culprits/1]). - -%%============================================================================ -%% Types -%%============================================================================ - -%%============================================================================ -%% Internal API -%%============================================================================ -%% @doc start running the solver, with each run reduce the number of constraints -%% set as goals. At some point the solver should succeed. --spec search(rcl_depsolver:dep_graph(), [rcl_depsolver:constraint()], [rcl_depsolver:constraint()]) - -> term(). -search(State, ActiveCons, []) -> - case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of - {fail, FailPaths} -> - extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); - _Success -> - %% This should *never* happen. 'Culprit' above represents the last - %% possible constraint that could cause things to fail. There for - %% this should have failed as well. - inconsistant_graph_state - end; -search(State, ActiveCons, [NewCon | Constraints]) -> - case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of - {fail, FailPaths} -> - extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); - _Success -> - %% Move one constraint from the inactive to the active - %% constraints and run again - search(State, [NewCon | ActiveCons], Constraints) - end. - -format_error({error, {unreachable_package, AppName}}) -> - ["Dependency ", format_constraint(AppName), " is specified as a dependency ", - "but is not reachable by the system.\n"]; -format_error({error, {invalid_constraints, Constraints}}) -> - ["Invalid constraint ", add_s(Constraints), " specified ", - lists:foldl(fun(Con, "") -> - [io_lib:format("~p", [Con])]; - (Con, Acc) -> - [io_lib:format("~p", [Con]), ", " | Acc] - end, "", Constraints)]; -format_error({error, Detail}) -> - format_error(Detail); -format_error(Details) when erlang:is_list(Details) -> - ["Unable to solve constraints, the following solutions were attempted \n\n", - [[format_error_path(" ", Detail)] || Detail <- Details]]. - --spec format_roots([rcl_depsolver:constraints()]) -> iolist(). -format_roots(Roots) -> - lists:foldl(fun(Root, Acc0) -> - lists:foldl( - fun(Con, "") -> - [format_constraint(Con)]; - (Con, Acc1) -> - [format_constraint(Con), ", " | Acc1] - end, Acc0, Root) - end, [], Roots). - --spec format_culprits([{[rcl_depsolver:constraint()], [rcl_depsolver:constraint()]}]) -> iolist(). -format_culprits(FailingDeps) -> - Deps = sets:to_list(sets:from_list(lists:flatten([[rcl_depsolver:dep_pkg(Con) || Con <- Cons] - || {_, Cons} <- FailingDeps]))), - lists:foldl(fun(Con, "") -> - [format_constraint(Con)]; - (Con, Acc1) -> - [format_constraint(Con), - ", " | Acc1] - end, [], Deps). - --spec format_version(rcl_depsolver:vsn()) -> iolist(). -format_version(Vsn) -> - ec_semver:format(Vsn). - --spec format_constraint(rcl_depsolver:constraint()) -> list(). -format_constraint(Pkg) when is_atom(Pkg) -> - erlang:atom_to_list(Pkg); -format_constraint(Pkg) when is_binary(Pkg) -> - erlang:binary_to_list(Pkg); -format_constraint({Pkg, Vsn}) when is_tuple(Vsn) -> - ["(", format_constraint(Pkg), " = ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '='}) when is_tuple(Vsn) -> - ["(", format_constraint(Pkg), " = ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, gte}) -> - ["(", format_constraint(Pkg), " >= ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '>='}) -> - ["(", format_constraint(Pkg), " >= ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, lte}) -> - ["(", format_constraint(Pkg), " <= ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '<='}) -> - ["(", format_constraint(Pkg), " <= ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, gt}) -> - ["(", format_constraint(Pkg), " > ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '>'}) -> - ["(", format_constraint(Pkg), " > ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, lt}) -> - ["(", format_constraint(Pkg), " < ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '<'}) -> - ["(", format_constraint(Pkg), " < ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, pes}) -> - ["(", format_constraint(Pkg), " ~> ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn, '~>'}) -> - ["(", format_constraint(Pkg), " ~> ", - format_version(Vsn), ")"]; -format_constraint({Pkg, Vsn1, Vsn2, between}) -> - ["(", format_constraint(Pkg), " between ", - format_version(Vsn1), " and ", - format_version(Vsn2), ")"]. - - -%%============================================================================ -%% Internal Functions -%%============================================================================ --spec append_value(term(), term(), proplists:proplist()) -> proplists:proplist(). -append_value(Key, Value, PropList) -> - case proplists:get_value(Key, PropList, undefined) of - undefined -> - [{Key, Value} | PropList]; - ExistingValue -> - [{Key, sets:to_list(sets:from_list([Value | ExistingValue]))} | - proplists:delete(Key, PropList)] - end. - --spec strip_goal([[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]) -> - [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]. -strip_goal([{'_GOAL_', 'NO_VSN'}, Children]) -> - Children; -strip_goal(All = [Val | _]) - when erlang:is_list(Val) -> - [strip_goal(Element) || Element <- All]; -strip_goal(Else) -> - Else. - --spec extract_culprit_information0(rcl_depsolver:constraints(), - [rcl_depsolver:fail_info()]) -> - [term()]. -extract_culprit_information0(ActiveCons, FailInfo) - when is_list(FailInfo) -> - [extract_culprit_information1(ActiveCons, FI) || FI <- FailInfo]. - - --spec extract_root(rcl_depsolver:constraints(), [rcl_depsolver:pkg()]) -> - {[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}. -extract_root(ActiveCons, TPath = [PRoot | _]) -> - RootName = rcl_depsolver:dep_pkg(PRoot), - Roots = lists:filter(fun(El) -> - RootName =:= rcl_depsolver:dep_pkg(El) - end, ActiveCons), - {Roots, TPath}. - --spec extract_culprit_information1(rcl_depsolver:constraints(), - rcl_depsolver:fail_info()) -> - term(). -extract_culprit_information1(_ActiveCons, {[], RawConstraints}) -> - %% In this case where there was no realized versions, the GOAL - %% constraints actually where unsatisfiable - Constraints = lists:flatten([Constraints || - {_, Constraints} <- RawConstraints]), - - Cons = [Pkg || {Pkg, Src} <- Constraints, - Src =:= {'_GOAL_', 'NO_VSN'}], - {[{Cons, Cons}], []}; -extract_culprit_information1(ActiveCons, {Path, RawConstraints}) -> - Constraints = lists:flatten([Constraints || - {_, Constraints} <- RawConstraints]), - FailCons = - lists:foldl(fun(El = {FailedPkg, FailedVsn}, Acc1) -> - case get_constraints(FailedPkg, FailedVsn, Path, - Constraints) of - [] -> - Acc1; - Cons -> - append_value(El, Cons, Acc1) - end - end, [], lists:reverse(Path)), - TreedPath = strip_goal(treeize_path({'_GOAL_', 'NO_VSN'}, Constraints, [])), - RunListItems = [extract_root(ActiveCons, TPath) || TPath <- TreedPath], - {RunListItems, FailCons}. - --spec follow_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), - {rcl_depsolver:constraint(), rcl_depsolver:pkg()}) -> - false | {ok, rcl_depsolver:constraint()}. -follow_chain(Pkg, Vsn, {{Pkg, Vsn}, {Pkg, Vsn}}) -> - %% When the package version is the same as the source we dont want to try to follow it at all - false; -follow_chain(Pkg, Vsn, {Con, {Pkg, Vsn}}) -> - {ok, Con}; -follow_chain(_Pkg, _Vsn, _) -> - false. - --spec find_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), - [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> - rcl_depsolver:constraints(). -find_chain(Pkg, Vsn, Constraints) -> - lists:foldl(fun(NCon, Acc) -> - case follow_chain(Pkg, Vsn, NCon) of - {ok, Con} -> - [Con | Acc]; - false -> - Acc - end - end, [], Constraints). - --spec get_constraints(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), [rcl_depsolver:pkg()], - [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> - rcl_depsolver:constraints(). -get_constraints(FailedPkg, FailedVsn, Path, Constraints) -> - Chain = find_chain(FailedPkg, FailedVsn, Constraints), - lists:filter(fun(Con) -> - PkgName = rcl_depsolver:dep_pkg(Con), - (lists:any(fun(PathEl) -> - not rcl_depsolver:filter_package(PathEl, Con) - end, Path) orelse - not lists:keymember(PkgName, 1, Path)) - end, Chain). - --spec pkg_vsn(rcl_depsolver:constraint(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}]) -> - [rcl_depsolver:pkg()]. -pkg_vsn(PkgCon, Constraints) -> - PkgName = rcl_depsolver:dep_pkg(PkgCon), - [DepPkg || Con = {DepPkg, _} <- Constraints, - case Con of - {Pkg = {PkgName, PkgVsn}, {PkgName, PkgVsn}} -> - rcl_depsolver:filter_package(Pkg, PkgCon); - _ -> - false - end]. - --spec depends(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}], - [rcl_depsolver:pkg()]) -> - [rcl_depsolver:pkg()]. -depends(SrcPkg, Constraints, Seen) -> - lists:flatten([pkg_vsn(Pkg, Constraints) || {Pkg, Source} <- Constraints, - Source =:= SrcPkg andalso - Pkg =/= SrcPkg andalso - not lists:member(Pkg, Seen)]). - --spec treeize_path(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}], - [rcl_depsolver:pkg()]) -> - [rcl_depsolver:pkg() | [rcl_depsolver:pkg()]]. -treeize_path(Pkg, Constraints, Seen0) -> - Seen1 = [Pkg | Seen0], - case depends(Pkg, Constraints, Seen1) of - [] -> - [Pkg]; - Deps -> - [Pkg, [treeize_path(Dep, Constraints, Seen1) || - Dep <- Deps]] - - end. - --spec add_s(list()) -> iolist(). -add_s(Roots) -> - case erlang:length(Roots) of - Len when Len > 1 -> - "s"; - _ -> - "" - end. - --spec format_path(string(), [rcl_depsolver:pkg()]) -> iolist(). -format_path(CurrentIdent, Path) -> - [CurrentIdent, " ", - lists:foldl(fun(Con, "") -> - [format_constraint(Con)]; - (Con, Acc) -> - [format_constraint(Con), " -> " | Acc] - end, "", Path), - "\n"]. - --spec format_dependency_paths(string(), [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()], - [{rcl_depsolver:pkg(), [rcl_depsolver:constraint()]}], [rcl_depsolver:pkg()]) -> iolist(). -format_dependency_paths(CurrentIndent, [SubPath | Rest], FailingDeps, Acc) - when erlang:is_list(SubPath) -> - [format_dependency_paths(CurrentIndent, lists:sort(SubPath), FailingDeps, Acc), - format_dependency_paths(CurrentIndent, Rest, FailingDeps, Acc)]; -format_dependency_paths(CurrentIndent, [Dep], FailingDeps, Acc) - when erlang:is_tuple(Dep) -> - case proplists:get_value(Dep, FailingDeps, undefined) of - undefined -> - format_path(CurrentIndent, [Dep | Acc]); - Cons -> - [format_path(CurrentIndent, [Con, Dep | Acc]) || Con <- Cons] - end; -format_dependency_paths(CurrentIndent, [Dep | Rest], FailingDeps, Acc) - when erlang:is_tuple(Dep) -> - case proplists:get_value(Dep, FailingDeps, undefined) of - undefined -> - format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Dep | Acc]); - Cons -> - [[format_path(CurrentIndent, [Con, Dep | Acc]) || Con <- Cons], - format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Dep | Acc])] - end; -format_dependency_paths(CurrentIndent, [Con | Rest], FailingDeps, Acc) -> - format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Con | Acc]); -format_dependency_paths(_CurrentIndent, [], _FailingDeps, _Acc) -> - []. - --spec format_error_path(string(), {[{[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}], - [rcl_depsolver:constraint()]}) -> iolist(). -format_error_path(CurrentIndent, {RawPaths, FailingDeps}) -> - Roots = [RootSet || {RootSet, _} <- RawPaths], - Paths = [Path || {_, Path} <- RawPaths], - [CurrentIndent, "Unable to satisfy goal constraint", - add_s(Roots), " ", format_roots(Roots), " due to constraint", add_s(FailingDeps), " on ", - format_culprits(FailingDeps), "\n", - format_dependency_paths(CurrentIndent, lists:sort(Paths), FailingDeps, []), ""]. diff --git a/src/rcl_dscv_util.erl b/src/rcl_dscv_util.erl deleted file mode 100644 index 68dcb68..0000000 --- a/src/rcl_dscv_util.erl +++ /dev/null @@ -1,142 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provider uses the lib_dir setting of the state. It searches the -%%% Lib Dirs looking for all OTP Applications that are available. When it finds -%%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_dscv_util). - --export([do/2, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% Types -%%============================================================================ - --type process_fun(Result) :: fun((filename:name(), file | directory) -> - {ok, Result} | - {error, term()} | - {ok, Result, Recurse::boolean()} | - {noresult, Recurse::boolean()} | - {error, term()}). - -%%============================================================================ -%% API -%%============================================================================ - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(process_fun([term()] | term()), [filename:name()]) -> - [term() | {error, term()}]. -do(ProcessDir, LibDirs) -> - lists:flatten(ec_plists:map(fun(LibDir) -> - discover_dir(ProcessDir, LibDir, - ec_file:type(LibDir)) - end, LibDirs)). - --spec format_error([ErrorDetail::term()]) -> iolist(). -format_error(ErrorDetails) - when erlang:is_list(ErrorDetails) -> - [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec format_detail(ErrorDetail::term()) -> iolist(). -format_detail({accessing, File, eaccess}) -> - io_lib:format("permission denied accessing file ~s", [File]); -format_detail({accessing, File, Type}) -> - io_lib:format("error (~p) accessing file ~s", [Type, File]); -format_detail({not_a_directory, EbinDir}) -> - io_lib:format("~s is not a directory when it should be a directory", [EbinDir]). - --spec discover_dir(process_fun([term()] | term()), - file:name(), - directory | file | symlink) -> - [{ok, term()} - | {error, Reason::term()}] - | [] - | {ok, term()} - | {error, Reason::term()}. -discover_dir(ProcessDir, File, directory) -> - case ProcessDir(File, directory) of - {ok, Result, true} -> - [{ok, Result} | recurse(ProcessDir, File)]; - {noresult, true} -> - recurse(ProcessDir, File); - {ok, Result, _} -> - [{ok, Result}]; - {noresult, _} -> - []; - Err = {error, _} -> - [Err] - end; -discover_dir(ProcessDir, File, file) -> - case ProcessDir(File, file) of - {ok, Result} -> - [{ok, Result}]; - {noresult, _} -> - []; - Err = {error, _} -> - [Err] - end; -discover_dir(ProcessDir, File, symlink) -> - case filelib:is_dir(File) of - false -> - discover_dir(ProcessDir, File, file); - true -> - discover_real_symlink_dir(ProcessDir, File) - end. - -discover_real_symlink_dir(ProcessDir, File) -> - {ok, CurCwd} = file:get_cwd(), - ok = file:set_cwd(File), - {ok, ActualRealDir} = file:get_cwd(), - ok = file:set_cwd(CurCwd), - lists:prefix(iolist_to_list(filename:absname(ActualRealDir)), - iolist_to_list(filename:absname(File))), - case ProcessDir(File, directory) of - {ok, Result, true} -> - [{ok, Result} | recurse(ProcessDir, File)]; - {noresult, true} -> - recurse(ProcessDir, File); - {ok, Result, _} -> - [{ok, Result}]; - {noresult, _} -> - []; - Err = {error, _} -> - [Err] - end. - -recurse(ProcessDir, File) -> - case file:list_dir(File) of - {error, Reason} -> - {error, {accessing, File, Reason}}; - {ok, List} -> - ec_plists:map(fun(LibDir) -> - discover_dir(ProcessDir, LibDir, ec_file:type(LibDir)) - end, [filename:join([File, Dir]) || Dir <- List]) - end. - -iolist_to_list(IoList) -> - erlang:binary_to_list(erlang:iolist_to_binary(IoList)). diff --git a/src/rcl_goal.peg b/src/rcl_goal.peg deleted file mode 100644 index bfac607..0000000 --- a/src/rcl_goal.peg +++ /dev/null @@ -1,55 +0,0 @@ - -constraint <- ws? app_name ws? between_op ws? version ws? "," ws? version ws? !. - / ws? app_name ws? constraint_op ws? version ws? !. - / ws? app_name ws? !. - - ` - case Node of - [_,AppName,_, _] -> - {ok, AppName}; - [_,AppName,_,Op,_,Vsn,_, _] -> - {ok, - {AppName, - rcl_goal_utils:to_vsn(Vsn), - rcl_goal_utils:to_op(Op)}}; - [_,AppName,_,Op,_,Vsn1,_,_,_,Vsn2,_,_] -> - {ok, - {AppName, - rcl_goal_utils:to_vsn(Vsn1), - rcl_goal_utils:to_vsn(Vsn2), - rcl_goal_utils:to_op(Op)}}; - _ -> - io:format("~p~n", [Node]) - end - ` ; - -ws <- [ \t\n\s\r] ; - -app_name <- [a-zA-Z0-9_]+ - ` erlang:list_to_atom(erlang:binary_to_list(erlang:iolist_to_binary(Node))) ` ; - -between_op <- - ":" ws? ( "btwn" / "between" ) ws? ":" - ` case Node of - [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); - _ -> Node - end - ` ; - -constraint_op <- "=" / "-" / "<=" / "<" / "~>" / ">=" / ">" / word_constraint_op / ":" ; - -word_constraint_op <- - ":" ws? ( "gte" / "lte" / "gt" / "lt" / "pes" ) ws? ":" - ` case Node of - [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); - _ -> Node - end - ` ; - - -version <- [0-9a-zA-Z-+.]+ ; - -%% This only exists to get around a bug in erlang where if -%% warnings_as_errors is specified `nowarn` directives are ignored - - `-compile(export_all).` \ No newline at end of file diff --git a/src/rcl_goal_utils.erl b/src/rcl_goal_utils.erl deleted file mode 100644 index 6065e5c..0000000 --- a/src/rcl_goal_utils.erl +++ /dev/null @@ -1,77 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc -%%% Utilities to help with parsing of target specs --module(rcl_goal_utils). - --export([to_op/1, - to_vsn/1]). - -%%============================================================================ -%% types -%%============================================================================ - -%%============================================================================ -%% API -%%============================================================================ --spec to_op(iolist()) -> rcl_depsolver:constraint_op(). -to_op(List) - when erlang:is_list(List) -> - to_op(erlang:iolist_to_binary(List)); -to_op(<<"=">>) -> - '='; -to_op(<<":">>) -> - '='; -to_op(<<"-">>) -> - '='; -to_op(<<"<">>) -> - 'lt'; -to_op(<<":lt:">>) -> - 'lt'; -to_op(<<"<=">>) -> - 'lte'; -to_op(<<":lte:">>) -> - 'lte'; -to_op(<<">">>) -> - 'gt'; -to_op(<<":gt:">>) -> - 'gt'; -to_op(<<">=">>) -> - 'gte'; -to_op(<<":gte:">>) -> - 'gte'; -to_op(<<"~>">>) -> - 'pes'; -to_op(<<":pes:">>) -> - 'pes'; -to_op(<<":btwn:">>) -> - 'between'; -to_op(<<":between:">>) -> - 'between'. - -to_vsn(Version) when erlang:is_list(Version) -> - to_vsn(erlang:iolist_to_binary(Version)); -to_vsn(Vsn) -> - rcl_depsolver:parse_version(Vsn). - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== diff --git a/src/rcl_log.erl b/src/rcl_log.erl deleted file mode 100644 index 71c0b5d..0000000 --- a/src/rcl_log.erl +++ /dev/null @@ -1,204 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provides simple output functions for relcool. You should use this -%%% to talk to the users if you are wrting code for the system --module(rcl_log). - --export([new/1, - log/4, - should/2, - debug/2, - debug/3, - info/2, - info/3, - error/2, - error/3, - log_level/1, - atom_log_level/1, - format/1]). - --export_type([int_log_level/0, - atom_log_level/0, - log_level/0, - log_fun/0, - t/0]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% types -%%============================================================================ - --type log_level() :: int_log_level() | atom_log_level(). - --type int_log_level() :: 0..2. - -%% Why no warn? because for our purposes there is no difference between error -%% and warn --type atom_log_level() :: error | info | debug. - --opaque t() :: {?MODULE, int_log_level()}. - --type log_fun() :: fun(() -> iolist()). - -%%============================================================================ -%% API -%%============================================================================ -%% @doc Create a new 'log level' for the system --spec new(log_level()) -> t(). -new(LogLevel) when LogLevel >= 0, LogLevel =< 2 -> - {?MODULE, LogLevel}; -new(AtomLogLevel) - when AtomLogLevel =:= error; - AtomLogLevel =:= info; - AtomLogLevel =:= debug -> - LogLevel = case AtomLogLevel of - error -> 0; - info -> 1; - debug -> 2 - end, - new(LogLevel). - - -%% @doc log at the debug level given the current log state with a string or -%% function that returns a string --spec debug(t(), string() | log_fun()) -> ok. -debug(LogState, Fun) - when erlang:is_function(Fun) -> - log(LogState, ?RCL_DEBUG, Fun); -debug(LogState, String) -> - debug(LogState, "~s~n", [String]). - -%% @doc log at the debug level given the current log state with a format string -%% and argements @see io:format/2 --spec debug(t(), string(), [any()]) -> ok. -debug(LogState, FormatString, Args) -> - log(LogState, ?RCL_DEBUG, FormatString, Args). - -%% @doc log at the info level given the current log state with a string or -%% function that returns a string --spec info(t(), string() | log_fun()) -> ok. -info(LogState, Fun) - when erlang:is_function(Fun) -> - log(LogState, ?RCL_INFO, Fun); -info(LogState, String) -> - info(LogState, "~s~n", [String]). - -%% @doc log at the info level given the current log state with a format string -%% and argements @see io:format/2 --spec info(t(), string(), [any()]) -> ok. -info(LogState, FormatString, Args) -> - log(LogState, ?RCL_INFO, FormatString, Args). - -%% @doc log at the error level given the current log state with a string or -%% format string that returns a function --spec error(t(), string() | log_fun()) -> ok. -error(LogState, Fun) - when erlang:is_function(Fun) -> - log(LogState, ?RCL_ERROR, Fun); -error(LogState, String) -> - error(LogState, "~s~n", [String]). - -%% @doc log at the error level given the current log state with a format string -%% and argements @see io:format/2 --spec error(t(), string(), [any()]) -> ok. -error(LogState, FormatString, Args) -> - log(LogState, ?RCL_ERROR, FormatString, Args). - -%% @doc Execute the fun passed in if log level is as expected. --spec log(t(), int_log_level(), log_fun()) -> ok. -log({?MODULE, DetailLogLevel}, LogLevel, Fun) - when DetailLogLevel >= LogLevel -> - io:format("~s~n", [Fun()]); -log(_, _, _) -> - ok. - - -%% @doc when the module log level is less then or equal to the log level for the -%% call then write the log info out. When its not then ignore the call. --spec log(t(), int_log_level(), string(), [any()]) -> ok. -log({?MODULE, DetailLogLevel}, LogLevel, FormatString, Args) - when DetailLogLevel >= LogLevel, - erlang:is_list(Args) -> - io:format(FormatString, Args); -log(_, _, _, _) -> - ok. - -%% @doc return a boolean indicating if the system should log for the specified -%% levelg --spec should(t(), int_log_level() | any()) -> boolean(). -should({?MODULE, DetailLogLevel}, LogLevel) - when DetailLogLevel >= LogLevel -> - true; -should(_, _) -> - false. - -%% @doc get the current log level as an integer --spec log_level(t()) -> int_log_level(). -log_level({?MODULE, DetailLogLevel}) -> - DetailLogLevel. - -%% @doc get the current log level as an atom --spec atom_log_level(t()) -> atom_log_level(). -atom_log_level({?MODULE, ?RCL_ERROR}) -> - error; -atom_log_level({?MODULE, ?RCL_INFO}) -> - info; -atom_log_level({?MODULE, ?RCL_DEBUG}) -> - debug. - --spec format(t()) -> iolist(). -format(Log) -> - [<<"(">>, - erlang:integer_to_list(log_level(Log)), <<":">>, - erlang:atom_to_list(atom_log_level(Log)), - <<")">>]. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - -should_test() -> - ErrorLogState = new(error), - ?assertMatch(true, should(ErrorLogState, ?RCL_ERROR)), - ?assertMatch(true, not should(ErrorLogState, ?RCL_INFO)), - ?assertMatch(true, not should(ErrorLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_ERROR, log_level(ErrorLogState)), - ?assertEqual(error, atom_log_level(ErrorLogState)), - - InfoLogState = new(info), - ?assertMatch(true, should(InfoLogState, ?RCL_ERROR)), - ?assertMatch(true, should(InfoLogState, ?RCL_INFO)), - ?assertMatch(true, not should(InfoLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_INFO, log_level(InfoLogState)), - ?assertEqual(info, atom_log_level(InfoLogState)), - - DebugLogState = new(debug), - ?assertMatch(true, should(DebugLogState, ?RCL_ERROR)), - ?assertMatch(true, should(DebugLogState, ?RCL_INFO)), - ?assertMatch(true, should(DebugLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_DEBUG, log_level(DebugLogState)), - ?assertEqual(debug, atom_log_level(DebugLogState)). - --endif. diff --git a/src/rcl_provider.erl b/src/rcl_provider.erl deleted file mode 100644 index 4d8f044..0000000 --- a/src/rcl_provider.erl +++ /dev/null @@ -1,117 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright 2011 Erlware, LLC. -%%% @doc -%%% A module that supports providing state manipulation services to the system. -%%% @end -%%%------------------------------------------------------------------- --module(rcl_provider). - -%% API --export([new/2, - do/2, - impl/1, - format_error/1, - format_error/2, - format/1]). - --export_type([t/0]). - --include_lib("relcool/include/relcool.hrl"). - -%%%=================================================================== -%%% Types -%%%=================================================================== - --opaque t() :: {?MODULE, module()}. - - --ifdef(have_callback_support). - --callback init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). --callback do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). --callback format_error(Reason::term()) -> iolist(). - --else. - -%% In the case where R14 or lower is being used to compile the system -%% we need to export a behaviour info --export([behaviour_info/1]). --spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. -behaviour_info(callbacks) -> - [{init, 1}, - {do, 1}, - {format_error, 1}]; -behaviour_info(_) -> - undefined. - --endif. - -%%%=================================================================== -%%% API -%%%=================================================================== - -%% @doc create a new provider object from the specified module. The -%% module should implement the provider behaviour. -%% -%% @param ModuleName The module name. -%% @param State0 The current state of the system --spec new(module(), rcl_state:t()) -> - {t(), {ok, rcl_state:t()}} | relcool:error(). -new(ModuleName, State0) when is_atom(ModuleName) -> - State1 = ModuleName:init(State0), - case code:which(ModuleName) of - non_existing -> - ?RCL_ERROR({non_existing, ModuleName}); - _ -> - {{?MODULE, ModuleName}, State1} - end. - -%% @doc Manipulate the state of the system, that new state -%% -%% @param Provider the provider object -%% @param State the current state of the system --spec do(Provider::t(), rcl_state:t()) -> - {ok, rcl_state:t()} | relcool:error(). -do({?MODULE, Mod}, State) -> - Mod:do(State). - -%%% @doc get the name of the module that implements the provider -%%% @param Provider the provider object --spec impl(Provider::t()) -> module(). -impl({?MODULE, Mod}) -> - Mod. - -%% @doc format an error produced from a provider. --spec format_error(Reason::term()) -> iolist(). -format_error({non_existing, ModuleName}) -> - io_lib:format("~p does not exist in the system", [ModuleName]). - -%% @doc format an error produced from a provider. --spec format_error(t(), Reason::term()) -> iolist(). -format_error({?MODULE, Mod}, Error) -> - Mod:format_error(Error). - -%% @doc print the provider module name -%% -%% @param T - The provider -%% @return An iolist describing the provider --spec format(t()) -> iolist(). -format({?MODULE, Mod}) -> - erlang:atom_to_list(Mod). diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl deleted file mode 100644 index aca783d..0000000 --- a/src/rcl_prv_assembler.erl +++ /dev/null @@ -1,1003 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc Given a complete built release this provider assembles that release -%%% into a release directory. --module(rcl_prv_assembler). - --behaviour(rcl_provider). - --export([init/1, - do/1, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. -init(State) -> - {ok, State}. - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). -do(State) -> - {RelName, RelVsn} = rcl_state:default_configured_release(State), - Release = rcl_state:get_realized_release(State, RelName, RelVsn), - OutputDir = rcl_state:output_dir(State), - case create_output_dir(OutputDir) of - ok -> - case rcl_release:realized(Release) of - true -> - copy_app_directories_to_output(State, Release, OutputDir); - false -> - ?RCL_ERROR({unresolved_release, RelName, RelVsn}) - end; - Error -> - Error - end. - --spec format_error(ErrorDetail::term()) -> iolist(). -format_error({unresolved_release, RelName, RelVsn}) -> - io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); -format_error({ec_file_error, AppDir, TargetDir, E}) -> - io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", - [AppDir, TargetDir, E]); -format_error({config_does_not_exist, Path}) -> - io_lib:format("The config file specified for this release (~s) does not exist!", - [Path]); -format_error({specified_erts_does_not_exist, ErtsVersion}) -> - io_lib:format("Specified version of erts (~s) does not exist", - [ErtsVersion]); -format_error({release_script_generation_error, RelFile}) -> - io_lib:format("Unknown internal release error generating the release file to ~s", - [RelFile]); -format_error({release_script_generation_warning, Module, Warnings}) -> - ["Warnings generating release \s", - rcl_util:indent(1), Module:format_warning(Warnings)]; -format_error({unable_to_create_output_dir, OutputDir}) -> - io_lib:format("Unable to create output directory (possible permissions issue): ~s", - [OutputDir]); -format_error({release_script_generation_error, Module, Errors}) -> - ["Errors generating release \n", - rcl_util:indent(1), Module:format_error(Errors)]; -format_error({relup_generation_error, CurrentName, UpFromName}) -> - io_lib:format("Unknown internal release error generating the relup from ~s to ~s", - [UpFromName, CurrentName]); -format_error({relup_generation_warning, Module, Warnings}) -> - ["Warnings generating relup \s", - rcl_util:indent(1), Module:format_warning(Warnings)]; -format_error({relup_script_generation_error, - {relupcript_generation_error, systools_relup, - {missing_sasl, _}}}) -> - "Unfortunately, due to requirements in systools, you need to have the sasl application " - "in both the current release and the release to upgrade from."; -format_error({relup_script_generation_error, Module, Errors}) -> - ["Errors generating relup \n", - rcl_util:indent(1), Module:format_error(Errors)]; -format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> - io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", - [AppDir, TargetDir, rcl_util:indent(1), - file:format_error(Reason)]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec create_output_dir(file:name()) -> - ok | {error, Reason::term()}. -create_output_dir(OutputDir) -> - case filelib:is_dir(OutputDir) of - false -> - case rcl_util:mkdir_p(OutputDir) of - ok -> - ok; - {error, _} -> - ?RCL_ERROR({unable_to_create_output_dir, OutputDir}) - end; - true -> - ok - end. - -copy_app_directories_to_output(State, Release, OutputDir) -> - LibDir = filename:join([OutputDir, "lib"]), - ok = ec_file:mkdir_p(LibDir), - Apps = rcl_release:application_details(Release), - Result = lists:filter(fun({error, _}) -> - true; - (_) -> - false - end, - lists:flatten(ec_plists:map(fun(App) -> - copy_app(LibDir, App) - end, Apps))), - case Result of - [E | _] -> - E; - [] -> - create_release_info(State, Release, OutputDir) - end. - -copy_app(LibDir, App) -> - AppName = erlang:atom_to_list(rcl_app_info:name(App)), - AppVsn = rcl_app_info:vsn_as_string(App), - AppDir = rcl_app_info:dir(App), - TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), - if - AppDir == TargetDir -> - %% No need to do anything here, discover found something already in - %% a release dir - ok; - true -> - copy_app(App, AppDir, TargetDir) - end. - -copy_app(App, AppDir, TargetDir) -> - remove_symlink_or_directory(TargetDir), - case rcl_app_info:link(App) of - true -> - link_directory(AppDir, TargetDir); - false -> - copy_directory(AppDir, TargetDir) - end. - -remove_symlink_or_directory(TargetDir) -> - case ec_file:is_symlink(TargetDir) of - true -> - ec_file:remove(TargetDir); - false -> - case filelib:is_dir(TargetDir) of - true -> - ok = ec_file:remove(TargetDir, [recursive]); - false -> - ok - end - end. - -link_directory(AppDir, TargetDir) -> - case file:make_symlink(AppDir, TargetDir) of - {error, Reason} -> - ?RCL_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); - ok -> - ok - end. - -copy_directory(AppDir, TargetDir) -> - ec_plists:map(fun(SubDir) -> - copy_dir(AppDir, TargetDir, SubDir) - end, ["ebin", - "include", - "priv", - "src", - "c_src", - "README", - "LICENSE"]). - -copy_dir(AppDir, TargetDir, SubDir) -> - SubSource = filename:join(AppDir, SubDir), - SubTarget = filename:join(TargetDir, SubDir), - case filelib:is_dir(SubSource) of - true -> - ok = rcl_util:mkdir_p(SubTarget), - case ec_file:copy(SubSource, SubTarget, [recursive]) of - {error, E} -> - ?RCL_ERROR({ec_file_error, AppDir, SubTarget, E}); - ok -> - ok - end; - false -> - ok - end. - -create_release_info(State0, Release0, OutputDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release0)), - ReleaseDir = release_output_dir(State0, Release0), - ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), - ok = ec_file:mkdir_p(ReleaseDir), - Release1 = rcl_release:relfile(Release0, ReleaseFile), - State1 = rcl_state:update_realized_release(State0, Release1), - case rcl_release:metadata(Release1) of - {ok, Meta} -> - ok = ec_file:write_term(ReleaseFile, Meta), - write_bin_file(State1, Release1, OutputDir, ReleaseDir); - E -> - E - end. - - -write_bin_file(State, Release, OutputDir, RelDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release)), - RelVsn = rcl_release:vsn(Release), - BinDir = filename:join([OutputDir, "bin"]), - ok = ec_file:mkdir_p(BinDir), - VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), - BareRel = filename:join(BinDir, RelName), - ErlOpts = rcl_state:get(State, erl_opts, ""), - StartFile = case rcl_state:get(State, extended_start_script, false) of - false -> - bin_file_contents(RelName, RelVsn, - rcl_release:erts(Release), - ErlOpts); - true -> - extended_bin_file_contents(RelName, RelVsn, rcl_release:erts(Release), ErlOpts) - end, - %% We generate the start script by default, unless the user - %% tells us not too - case rcl_state:get(State, generate_start_script, true) of - false -> - ok; - _ -> - ok = file:write_file(VsnRel, StartFile), - ok = file:change_mode(VsnRel, 8#777), - ok = file:write_file(BareRel, StartFile), - ok = file:change_mode(BareRel, 8#777) - end, - ok = file:write_file(filename:join(RelDir, "vm.args"), vm_args_file(RelName)), - copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir). - -%% @doc copy config/sys.config or generate one to releases/VSN/sys.config --spec copy_or_generate_sys_config_file(rcl_state:t(), rcl_release:t(), - file:name(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). -copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir) -> - RelSysConfPath = filename:join([RelDir, "sys.config"]), - case rcl_state:sys_config(State) of - undefined -> - ok = generate_sys_config_file(RelSysConfPath), - include_erts(State, Release, OutputDir, RelDir); - ConfigPath -> - case filelib:is_regular(ConfigPath) of - false -> - ?RCL_ERROR({config_does_not_exist, ConfigPath}); - true -> - ok = ec_file:copy(ConfigPath, RelSysConfPath), - include_erts(State, Release, OutputDir, RelDir) - end - end. - -%% @doc write a generic sys.config to the path RelSysConfPath --spec generate_sys_config_file(string()) -> ok. -generate_sys_config_file(RelSysConfPath) -> - {ok, Fd} = file:open(RelSysConfPath, [write]), - io:format(Fd, - "%% Thanks to Ulf Wiger at Ericcson for these comments:~n" - "%%~n" - "%% This file is identified via the erl command line option -config File.~n" - "%% Note that File should have no extension, e.g.~n" - "%% erl -config .../sys (if this file is called sys.config)~n" - "%%~n" - "%% In this file, you can redefine application environment variables.~n" - "%% This way, you don't have to modify the .app files of e.g. OTP applications.~n" - "[].~n", []), - file:close(Fd). - -%% @doc Optionally add erts directory to release, if defined. --spec include_erts(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> {ok, rcl_state:t()} | relcool:error(). -include_erts(State, Release, OutputDir, RelDir) -> - case rcl_state:get(State, include_erts, true) of - true -> - Prefix = code:root_dir(), - ErtsVersion = rcl_release:erts(Release), - ErtsDir = filename:join([Prefix, "erts-" ++ ErtsVersion]), - LocalErts = filename:join([OutputDir, "erts-" ++ ErtsVersion]), - case filelib:is_dir(ErtsDir) of - false -> - ?RCL_ERROR({specified_erts_does_not_exist, ErtsVersion}); - true -> - ok = ec_file:mkdir_p(LocalErts), - ok = ec_file:copy(ErtsDir, LocalErts, [recursive]), - ok = ec_file:remove(filename:join([LocalErts, "bin", "erl"])), - ok = file:write_file(filename:join([LocalErts, "bin", "erl"]), erl_script(ErtsVersion)), - case rcl_state:get(State, extended_start_script, false) of - true -> - ok = ec_file:copy(filename:join([Prefix, "bin", "start_clean.boot"]), - filename:join([OutputDir, "bin", "start_clean.boot"])), - NodeToolFile = nodetool_contents(), - NodeTool = filename:join([LocalErts, "bin", "nodetool"]), - ok = file:write_file(NodeTool, NodeToolFile), - ok = file:change_mode(NodeTool, 8#755); - false -> - ok - end, - make_boot_script(State, Release, OutputDir, RelDir) - end; - _ -> - make_boot_script(State, Release, OutputDir, RelDir) - end. - - --spec make_boot_script(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). -make_boot_script(State, Release, OutputDir, RelDir) -> - Options = [{path, [RelDir | get_code_paths(Release, OutputDir)]}, - {outdir, RelDir}, - no_module_tests, silent], - Name = erlang:atom_to_list(rcl_release:name(Release)), - ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), - case make_script(Options, - fun(CorrectedOptions) -> - systools:make_script(Name, CorrectedOptions) - end) of - ok -> - rcl_log:error(rcl_state:log(State), - "release successfully created!"), - make_relup(State, Release); - error -> - ?RCL_ERROR({release_script_generation_error, ReleaseFile}); - {ok, _, []} -> - rcl_log:error(rcl_state:log(State), - "release successfully created!"), - make_relup(State, Release); - {ok,Module,Warnings} -> - ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); - {error,Module,Error} -> - ?RCL_ERROR({release_script_generation_error, Module, Error}) - end. - --spec make_script([term()], - fun(([term()]) -> Res)) -> Res. -make_script(Options, RunFun) -> - %% Erts 5.9 introduced a non backwards compatible option to - %% erlang this takes that into account - Erts = erlang:system_info(version), - case ec_semver:gte(Erts, "5.9") of - true -> - RunFun([no_warn_sasl | Options]); - _ -> - RunFun(Options) - end. - -make_relup(State, Release) -> - case rcl_state:action(State) of - relup -> - UpFrom = - case rcl_state:upfrom(State) of - undefined -> - get_last_release(State, Release); - Vsn -> - get_up_release(State, Release, Vsn) - end, - case UpFrom of - undefined -> - ?RCL_ERROR(no_upfrom_release_found); - _ -> - make_upfrom_script(State, Release, UpFrom) - end; - _ -> - {ok, State} - end. - -make_upfrom_script(State, Release, UpFrom) -> - OutputDir = rcl_state:output_dir(State), - Options = [{outdir, OutputDir}, - {path, get_code_paths(Release, OutputDir) ++ - get_code_paths(UpFrom, OutputDir)}, - silent], - CurrentRel = strip_rel(rcl_release:relfile(Release)), - UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), - rcl_log:debug(rcl_state:log(State), - "systools:make_relup(~p, ~p, ~p, ~p)", - [CurrentRel, UpFromRel, UpFromRel, Options]), - case make_script(Options, - fun(CorrectOptions) -> - systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) - end) of - ok -> - rcl_log:error(rcl_state:log(State), - "relup from ~s to ~s successfully created!", - [UpFromRel, CurrentRel]), - {ok, State}; - error -> - ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); - {ok, RelUp, _, []} -> - rcl_log:error(rcl_state:log(State), - "relup successfully created!"), - write_relup_file(State, Release, RelUp), - {ok, State}; - {ok,_, Module,Warnings} -> - ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); - {error,Module,Errors} -> - ?RCL_ERROR({relupcript_generation_error, Module, Errors}) - end. - -write_relup_file(State, Release, Relup) -> - OutDir = release_output_dir(State, Release), - RelName = rcl_util:to_string(rcl_release:name(Release)), - RelupFile = filename:join(OutDir, RelName ++ ".relup"), - ok = ec_file:write_term(RelupFile, Relup). - -strip_rel(Name) -> - rcl_util:to_string(filename:join(filename:dirname(Name), - filename:basename(Name, ".rel"))). - - -get_up_release(State, Release, Vsn) -> - Name = rcl_release:name(Release), - try - ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) - catch - throw:notfound -> - undefined - end. - -get_last_release(State, Release) -> - Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], - Releases1 = lists:sort(fun(R1, R2) -> - ec_semver:lte(rcl_release:vsn(R1), - rcl_release:vsn(R2)) - end, Releases0), - Res = lists:foldl(fun(_Rel, R = {found, _}) -> - R; - (Rel, Prev) -> - case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of - true -> - {found, Prev}; - false -> - Rel - end - end, undefined, Releases1), - case Res of - {found, R} -> - R; - Else -> - Else - end. - --spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). -release_output_dir(State, Release) -> - OutputDir = rcl_state:output_dir(State), - filename:join([OutputDir, - "releases", - rcl_release:canonical_name(Release)]). - -%% @doc Generates the correct set of code paths for the system. --spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. -get_code_paths(Release, OutDir) -> - LibDir = filename:join(OutDir, "lib"), - [filename:join([LibDir, - erlang:atom_to_list(rcl_app_info:name(App)) ++ "-" ++ - rcl_app_info:vsn_as_string(App), "ebin"]) || - App <- rcl_release:application_details(Release)]. - -erl_script(ErtsVsn) -> - [<<"#!/bin/sh -set -e - -SCRIPT_DIR=`dirname $0` -ROOTDIR=`cd $SCRIPT_DIR/../../ && pwd` -BINDIR=$ROOTDIR/erts-">>, ErtsVsn, <<"/bin -EMU=beam -PROGNAME=`echo $0 | sed 's/.*\\///'` -export EMU -export ROOTDIR -export BINDIR -export PROGNAME -exec \"$BINDIR/erlexec\" ${1+\"$@\"} -">>]. - -bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> - [<<"#!/bin/sh - -set -e - -SCRIPT_DIR=`dirname $0` -RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` -REL_NAME=">>, RelName, <<" -REL_VSN=">>, RelVsn, <<" -ERTS_VSN=">>, ErtsVsn, <<" -REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN -ERL_OPTS=">>, ErlOpts, <<" - -find_erts_dir() { - local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN - if [ -d \"$erts_dir\" ]; then - ERTS_DIR=$erts_dir; - ROOTDIR=$RELEASE_ROOT_DIR - else - local erl=`which erl` - local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` - ERTS_DIR=$erl_root/erts-$ERTS_VSN - ROOTDIR=$erl_root - fi - -} - -find_sys_config() { - local possible_sys=$REL_DIR/sys.config - if [ -f \"$possible_sys\" ]; then - SYS_CONFIG=\"-config $possible_sys\" - fi -} - -find_erts_dir -find_sys_config -export ROOTDIR=$RELEASE_ROOT_DIR -export BINDIR=$ERTS_DIR/bin -export EMU=beam -export PROGNAME=erl -export LD_LIBRARY_PATH=$ERTS_DIR/lib - -cd $ROOTDIR - -$BINDIR/erlexec $ERL_OPTS $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@">>]. - -extended_bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> - [<<"#!/bin/sh - -set -e - -SCRIPT_DIR=`dirname $0` -RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` -REL_NAME=">>, RelName, <<" -REL_VSN=">>, RelVsn, <<" -ERTS_VSN=">>, ErtsVsn, <<" -REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN -ERL_OPTS=">>, ErlOpts, <<" -PIPE_DIR=/tmp/$REL_DIR/ - -find_erts_dir() { - local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN - if [ -d \"$erts_dir\" ]; then - ERTS_DIR=$erts_dir; - ROOTDIR=$RELEASE_ROOT_DIR - else - local erl=`which erl` - local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` - ERTS_DIR=$erl_root/erts-$ERTS_VSN - ROOTDIR=$erl_root - fi - -} - -find_sys_config() { - local possible_sys=$REL_DIR/sys.config - if [ -f \"$possible_sys\" ]; then - SYS_CONFIG=\"-config $possible_sys\" - fi -} - -# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or else etc/vm.args -if [ -e \"$CALLER_DIR/vm.args\" ]; then - VMARGS_PATH=$CALLER_DIR/vm.args - USE_DIR=$CALLER_DIR -else - USE_DIR=$REL_DIR - if [ -e \"$REL_DIR/vm.args\" ]; then - VMARGS_PATH=\"$REL_DIR/vm.args\" - else - VMARGS_PATH=\"$REL_DIR/vm.args\" - fi -fi - -RUNNER_LOG_DIR=$USE_DIR/log -# Make sure log directory exists -mkdir -p $RUNNER_LOG_DIR - -# Use releases/VSN/sys.config if it exists otherwise use etc/app.config -if [ -e \"$USE_DIR/sys.config\" ]; then - CONFIG_PATH=\"$USE_DIR/sys.config\" -else - if [ -e \"$REL_DIR/sys.config\" ]; then - CONFIG_PATH=\"$REL_DIR/sys.config\" - else - CONFIG_PATH=\"$REL_DIR/app.config\" - fi -fi - -# Extract the target node name from node.args -NAME_ARG=`egrep '^-s?name' $VMARGS_PATH` -if [ -z \"$NAME_ARG\" ]; then - echo \"vm.args needs to have either -name or -sname parameter.\" - exit 1 -fi - -# Extract the name type and name from the NAME_ARG for REMSH -REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` -REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` - -# Note the `date +%s`, used to allow multiple remsh to the same node transparently -REMSH_NAME_ARG=\"$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`\" -REMSH_REMSH_ARG=\"-remsh $REMSH_NAME\" - -# Extract the target cookie -COOKIE_ARG=`grep '^-setcookie' $VMARGS_PATH` -if [ -z \"$COOKIE_ARG\" ]; then - echo \"vm.args needs to have a -setcookie parameter.\" - exit 1 -fi - -# Setup remote shell command to control node -REMSH=\"$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG\" - -find_erts_dir -find_sys_config -export ROOTDIR=$RELEASE_ROOT_DIR -export BINDIR=$ERTS_DIR/bin -export EMU=beam -export PROGNAME=erl -export LD_LIBRARY_PATH=$ERTS_DIR/lib - -cd $ROOTDIR - -# Setup command to control the node -NODETOOL=\"$ERTS_DIR/bin/escript $ERTS_DIR/bin/nodetool $NAME_ARG $COOKIE_ARG\" - -# Check the first argument for instructions -case \"$1\" in - start|start_boot) - - # Make sure there is not already a node running - #RES=`$NODETOOL ping` - #if [ \"$RES\" = \"pong\" ]; then - # echo \"Node is already running!\" - # exit 1 - #fi - case \"$1\" in - start) - shift - START_OPTION=\"console\" - HEART_OPTION=\"start\" - ;; - start_boot) - shift - START_OPTION=\"console_boot\" - HEART_OPTION=\"start_boot\" - ;; - esac - RUN_PARAM=$(printf \"'%s' \" \"$@\") - HEART_COMMAND=\"$SCRIPT_DIR/bin/$REL_NAME $HEART_OPTION $RUN_PARAM\" - export HEART_COMMAND - mkdir -p $PIPE_DIR - $ERTS_DIR/bin/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR \"exec $RELEASE_ROOT_DIR/bin/$REL_NAME $START_OPTION $RUN_PARAM\" 2>&1 - ;; - - stop) - # Wait for the node to completely stop... - case `uname -s` in - Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) - # PID COMMAND - PID=`ps ax -o pid= -o command=| - grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` - ;; - SunOS) - # PID COMMAND - PID=`ps -ef -o pid= -o args=| - grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` - ;; - CYGWIN*) - # UID PID PPID TTY STIME COMMAND - PID=`ps -efW|grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $2}'` - ;; - esac - $NODETOOL stop - ES=$? - if [ \"$ES\" -ne 0 ]; then - exit $ES - fi - while `kill -0 $PID 2>/dev/null`; - do - sleep 1 - done - ;; - - restart) - ## Restart the VM without exiting the process - $NODETOOL restart - ES=$? - if [ \"$ES\" -ne 0 ]; then - exit $ES - fi - ;; - - reboot) - ## Restart the VM completely (uses heart to restart it) - $NODETOOL reboot - ES=$? - if [ \"$ES\" -ne 0 ]; then - exit $ES - fi - ;; - - ping) - ## See if the VM is alive - $NODETOOL ping - ES=$? - if [ \"$ES\" -ne 0 ]; then - exit $ES - fi - ;; - - attach) - # Make sure a node IS running - RES=`$NODETOOL ping` - ES=$? - if [ \"$ES\" -ne 0 ]; then - echo \"Node is not running!\" - exit $ES - fi - - shift - exec $ERTS_DIR/bin/to_erl $PIPE_DIR - ;; - - remote_console) - # Make sure a node IS running - RES=`$NODETOOL ping` - ES=$? - if [ \"$ES\" -ne 0 ]; then - echo \"Node is not running!\" - exit $ES - fi - - shift - exec $REMSH - ;; - - upgrade) - if [ -z \"$2\" ]; then - echo \"Missing upgrade package argument\" - echo \"Usage: $REL_NAME upgrade {package base name}\" - echo \"NOTE {package base name} MUST NOT include the .tar.gz suffix\" - exit 1 - fi - - # Make sure a node IS running - RES=`$NODETOOL ping` - ES=$? - if [ \"$ES\" -ne 0 ]; then - echo \"Node is not running!\" - exit $ES - fi - - node_name=`echo $NAME_ARG | awk '{print $2}'` - erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` - - $ERTS_DIR/bin/escript $SCRIPT_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 - ;; - - console|console_clean|console_boot) - # .boot file typically just $REL_NAME (ie, the app name) - # however, for debugging, sometimes start_clean.boot is useful. - # For e.g. 'setup', one may even want to name another boot script. - case \"$1\" in - console) BOOTFILE=$REL_NAME ;; - console_clean) BOOTFILE=start_clean ;; - console_boot) - shift - BOOTFILE=\"$1\" - shift - ;; - esac - # Setup beam-required vars - ROOTDIR=$RELEASE_ROOT_DIR - BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin - EMU=beam - PROGNAME=`echo $0 | sed 's/.*\\///'` - CMD=\"$BINDIR/erlexec -boot $REL_DIR/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH\" - export EMU - export ROOTDIR - export BINDIR - export PROGNAME - - # Dump environment info for logging purposes - echo \"Exec: $CMD\" -- ${1+\"$@\"} - echo \"Root: $ROOTDIR\" - - # Log the startup - logger -t \"$REL_NAME[$$]\" \"Starting up\" - - # Start the VM - exec $CMD -- ${1+\"$@\"} - ;; - - foreground) - # start up the release in the foreground for use by runit - # or other supervision services - - BOOTFILE=$REL_NAME - FOREGROUNDOPTIONS=\"-noinput +Bd\" - - # Setup beam-required vars - ROOTDIR=$RELEASE_ROOT_DIR - BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin - EMU=beam - PROGNAME=`echo $0 | sed 's/.*///'` - CMD=\"$BINDIR/erlexec $FOREGROUNDOPTIONS -boot $REL_DIR/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH\" - export EMU - export ROOTDIR - export BINDIR - export PROGNAME - - # Dump environment info for logging purposes - echo \"Exec: $CMD\" -- ${1+\"$@\"} - echo \"Root: $ROOTDIR\" - - # Start the VM - exec $CMD -- ${1+\"$@\"} - ;; - *) - echo \"Usage: $REL_NAME {start|start_boot |foreground|stop|restart|reboot|ping|console|console_clean|console_boot |attach|remote_console|upgrade}\" - exit 1 - ;; -esac - -exit 0">>]. - -nodetool_contents() -> - [<<"%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ft=erlang ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% nodetool: Helper Script for interacting with live nodes -%% -%% ------------------------------------------------------------------- - -main(Args) -> - ok = start_epmd(), - %% Extract the args - {RestArgs, TargetNode} = process_args(Args, [], undefined), - - %% See if the node is currently running -- if it's not, we'll bail - case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of - {true, pong} -> - ok; - {_, pang} -> - io:format(\"Node ~p not responding to pings.\n\", [TargetNode]), - halt(1) - end, - - case RestArgs of - [\"ping\"] -> - %% If we got this far, the node already responsed to a ping, so just dump - %% a \"pong\" - io:format(\"pong\n\"); - [\"stop\"] -> - io:format(\"~p\n\", [rpc:call(TargetNode, init, stop, [], 60000)]); - [\"restart\"] -> - io:format(\"~p\n\", [rpc:call(TargetNode, init, restart, [], 60000)]); - [\"reboot\"] -> - io:format(\"~p\n\", [rpc:call(TargetNode, init, reboot, [], 60000)]); - [\"rpc\", Module, Function | RpcArgs] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), - [RpcArgs], 60000) of - ok -> - ok; - {badrpc, Reason} -> - io:format(\"RPC to ~p failed: ~p\n\", [TargetNode, Reason]), - halt(1); - _ -> - halt(1) - end; - [\"rpcterms\", Module, Function, ArgsAsString] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), - consult(ArgsAsString), 60000) of - {badrpc, Reason} -> - io:format(\"RPC to ~p failed: ~p\n\", [TargetNode, Reason]), - halt(1); - Other -> - io:format(\"~p\n\", [Other]) - end; - Other -> - io:format(\"Other: ~p\n\", [Other]), - io:format(\"Usage: nodetool {ping|stop|restart|reboot}\n\") - end, - net_kernel:stop(). - -process_args([], Acc, TargetNode) -> - {lists:reverse(Acc), TargetNode}; -process_args([\"-setcookie\", Cookie | Rest], Acc, TargetNode) -> - erlang:set_cookie(node(), list_to_atom(Cookie)), - process_args(Rest, Acc, TargetNode); -process_args([\"-name\", TargetName | Rest], Acc, _) -> - ThisNode = append_node_suffix(TargetName, \"_maint_\"), - {ok, _} = net_kernel:start([ThisNode, longnames]), - process_args(Rest, Acc, nodename(TargetName)); -process_args([\"-sname\", TargetName | Rest], Acc, _) -> - ThisNode = append_node_suffix(TargetName, \"_maint_\"), - {ok, _} = net_kernel:start([ThisNode, shortnames]), - process_args(Rest, Acc, nodename(TargetName)); -process_args([Arg | Rest], Acc, Opts) -> - process_args(Rest, [Arg | Acc], Opts). - - -start_epmd() -> - [] = os:cmd(epmd_path() ++ \" -daemon\"), - ok. - -epmd_path() -> - ErtsBinDir = filename:dirname(escript:script_name()), - Name = \"epmd\", - case os:find_executable(Name, ErtsBinDir) of - false -> - case os:find_executable(Name) of - false -> - io:format(\"Could not find epmd.~n\"), - halt(1); - GlobalEpmd -> - GlobalEpmd - end; - Epmd -> - Epmd - end. - - -nodename(Name) -> - case string:tokens(Name, \"@\") of - [_Node, _Host] -> - list_to_atom(Name); - [Node] -> - [_, Host] = string:tokens(atom_to_list(node()), \"@\"), - list_to_atom(lists:concat([Node, \"@\", Host])) - end. - -append_node_suffix(Name, Suffix) -> - case string:tokens(Name, \"@\") of - [Node, Host] -> - list_to_atom(lists:concat([Node, Suffix, os:getpid(), \"@\", Host])); - [Node] -> - list_to_atom(lists:concat([Node, Suffix, os:getpid()])) - end. - - -%% -%% Given a string or binary, parse it into a list of terms, ala file:consult/0 -%% -consult(Str) when is_list(Str) -> - consult([], Str, []); -consult(Bin) when is_binary(Bin)-> - consult([], binary_to_list(Bin), []). - -consult(Cont, Str, Acc) -> - case erl_scan:tokens(Cont, Str, 0) of - {done, Result, Remaining} -> - case Result of - {ok, Tokens, _} -> - {ok, Term} = erl_parse:parse_term(Tokens), - consult([], Remaining, [Term | Acc]); - {eof, _Other} -> - lists:reverse(Acc); - {error, Info, _} -> - {error, Info} - end; - {more, Cont1} -> - consult(Cont1, eof, Acc) - end.">>]. - -vm_args_file(RelName) -> - [<<"## Name of the node --name ">>, RelName, <<"@127.0.0.1 - -## Cookie for distributed erlang --setcookie ">>, RelName, <<" - -## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive -## (Disabled by default..use with caution!) -##-heart - -## Enable kernel poll and a few async threads -##+K true -##+A 5 - -## Increase number of concurrent ports/sockets -##-env ERL_MAX_PORTS 4096 - -## Tweak GC to run more often -##-env ERL_FULLSWEEP_AFTER 10">>]. diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl deleted file mode 100644 index 2bc9851..0000000 --- a/src/rcl_prv_config.erl +++ /dev/null @@ -1,186 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright 2011 Erlware, LLC. -%%% @doc -%%% A module that provides config parsing and support to the system -%%% @end -%%%------------------------------------------------------------------- --module(rcl_prv_config). - --behaviour(rcl_provider). - -%% API --export([init/1, - do/1, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== - -%% @doc Required by the system, but not used in this provider --spec init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). -init(State) -> - {ok, State}. - -%% @doc parse all the configs currently specified in the state, -%% populating the state as a result. --spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error(). -do(State) -> - case rcl_state:config_file(State) of - [] -> - search_for_dominating_config(State); - undefined -> - search_for_dominating_config(State); - ConfigFile when erlang:is_list(ConfigFile) -> - load_config(ConfigFile, State) - end. - --spec format_error(Reason::term()) -> iolist(). -format_error({consult, ConfigFile, Reason}) -> - io_lib:format("Unable to read file ~s: ~s", [ConfigFile, - file:format_error(Reason)]); -format_error({invalid_term, Term}) -> - io_lib:format("Invalid term in config file: ~p", [Term]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== -search_for_dominating_config({ok, Cwd}) -> - ConfigFile = filename:join(Cwd, "relcool.config"), - case ec_file:exists(ConfigFile) of - true -> - {ok, ConfigFile}; - false -> - search_for_dominating_config(parent_dir(Cwd)) - end; -search_for_dominating_config({error, _}) -> - no_config; -search_for_dominating_config(State0) -> - {ok, Cwd} = file:get_cwd(), - case search_for_dominating_config({ok, Cwd}) of - {ok, Config} -> - %% we need to set the root dir on state as well - {ok, RootDir} = parent_dir(Config), - State1 = rcl_state:root_dir(State0, RootDir), - load_config(Config, rcl_state:config_file(State1, Config)); - no_config -> - {ok, State0} - end. - -%% @doc Given a directory returns the name of the parent directory. --spec parent_dir(Filename::string()) -> - {ok, DirName::string()} | {error, no_parent_dir}. -parent_dir(Filename) -> - parent_dir(filename:split(Filename), []). - -%% @doc Given list of directories, splits the list and returns all dirs but the -%% last as a path. --spec parent_dir([string()], [string()]) -> - {ok, DirName::string()} | {error, no_parent_dir}. -parent_dir([_H], []) -> - {error, no_parent_dir}; -parent_dir([], []) -> - {error, no_parent_dir}; -parent_dir([_H], Acc) -> - {ok, filename:join(lists:reverse(Acc))}; -parent_dir([H | T], Acc) -> - parent_dir(T, [H | Acc]). - - --spec load_config(file:filename(), rcl_state:t()) -> - {ok, rcl_state:t()} | relcool:error(). -load_config(ConfigFile, State) -> - {ok, CurrentCwd} = file:get_cwd(), - ok = file:set_cwd(filename:dirname(ConfigFile)), - Result = case file:consult(ConfigFile) of - {error, Reason} -> - ?RCL_ERROR({consult, ConfigFile, Reason}); - {ok, Terms} -> - lists:foldl(fun load_terms/2, {ok, State}, Terms) - end, - ok = file:set_cwd(CurrentCwd), - Result. - --spec load_terms(term(), {ok, rcl_state:t()} | relcool:error()) -> - {ok, rcl_state:t()} | relcool:error(). -load_terms({default_release, RelName, RelVsn}, {ok, State}) -> - {ok, rcl_state:default_configured_release(State, RelName, RelVsn)}; -load_terms({paths, Paths}, {ok, State}) -> - code:add_pathsa([filename:absname(Path) || Path <- Paths]), - {ok, State}; -load_terms({providers, Providers0}, {ok, State0}) -> - Providers1 = gen_providers(Providers0, State0), - case Providers1 of - {_, E={error, _}} -> - E; - {Providers3, {ok, State3}} -> - {ok, rcl_state:providers(State3, Providers3)} - end; -load_terms({add_providers, Providers0}, {ok, State0}) -> - Providers1 = gen_providers(Providers0, State0), - case Providers1 of - {_, E={error, _}} -> - E; - {Providers3, {ok, State1}} -> - ExistingProviders = rcl_state:providers(State1), - {ok, rcl_state:providers(State1, ExistingProviders ++ Providers3)} - end; -load_terms({skip_apps, SkipApps0}, {ok, State0}) -> - {ok, rcl_state:skip_apps(State0, SkipApps0)}; -load_terms({overrides, Overrides0}, {ok, State0}) -> - {ok, rcl_state:overrides(State0, Overrides0)}; -load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) -> - Release0 = rcl_release:new(RelName, Vsn), - case rcl_release:goals(Release0, Applications) of - E={error, _} -> - E; - {ok, Release1} -> - {ok, rcl_state:add_configured_release(State0, Release1)} - end; -load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, - Applications}, {ok, State}) -> - Release0 = rcl_release:erts(rcl_release:new(RelName, Vsn), ErtsVsn), - case rcl_release:goals(Release0, Applications) of - E={error, _} -> - E; - {ok, Release1} -> - {ok, rcl_state:add_configured_release(State, Release1)} - end; -load_terms({sys_config, SysConfig}, {ok, State}) -> - {ok, rcl_state:sys_config(State, filename:absname(SysConfig))}; -load_terms({Name, Value}, {ok, State}) - when erlang:is_atom(Name) -> - {ok, rcl_state:put(State, Name, Value)}; -load_terms(_, Error={error, _}) -> - Error; -load_terms(InvalidTerm, _) -> - ?RCL_ERROR({invalid_term, InvalidTerm}). - --spec gen_providers([module()], rcl_state:t()) -> - {[rcl_provider:t()], {ok, rcl_state:t()} | relcool:error()}. -gen_providers(Providers, State) -> - lists:foldl(fun(ProviderName, {Providers1, {ok, State1}}) -> - {Provider, State2} = rcl_provider:new(ProviderName, State1), - {[Provider | Providers1], State2}; - (_, E={_, {error, _}}) -> - E - end, {[], {ok, State}}, Providers). diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl deleted file mode 100644 index 56085ac..0000000 --- a/src/rcl_prv_discover.erl +++ /dev/null @@ -1,133 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provider uses the lib_dir setting of the state. It searches the -%%% Lib Dirs looking for all OTP Applications that are available. When it finds -%%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_prv_discover). --behaviour(rcl_provider). - --export([init/1, - do/1, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. -init(State) -> - {ok, State}. - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). -do(State0) -> - LibDirs = get_lib_dirs(State0), - case rcl_app_discovery:do(State0, LibDirs) of - {ok, AppMeta} -> - case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of - {ok, Releases} -> - State1 = rcl_state:available_apps(State0, AppMeta), - {ok, rcl_state:realized_releases(State1, lists:foldl(fun add/2, - ec_dictionary:new(ec_dict), - Releases))}; - Error -> - Error - end; - Error -> - Error - end. - -%% @doc this is here to comply with the signature. However, we do not actually -%% produce any errors and so simply return an empty string. --spec format_error(any()) -> iolist(). -format_error(_) -> - "". - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== -%% @doc only add the release if its not documented in the system -add(Rel, Dict) -> - RelName = rcl_release:name(Rel), - RelVsn = rcl_release:vsn(Rel), - ec_dictionary:add({RelName, RelVsn}, Rel, Dict). - -get_lib_dirs(State) -> - LibDirs0 = rcl_state:lib_dirs(State), - case rcl_state:get(State, disable_default_libs, false) of - true -> - LibDirs0; - false -> - lists:flatten([LibDirs0, - add_common_project_dirs(State), - add_system_lib_dir(State), - add_release_output_dir(State)]) - end. - --spec add_common_project_dirs(rcl_state:t()) -> [file:name()]. -add_common_project_dirs(State) -> - %% Check to see if there is a rebar.config. If so then look for a deps - %% dir. If both are there then we add that to the lib dirs. - case rcl_state:get(State, disable_project_subdirs, false) of - true -> - []; - false -> - Root = rcl_state:root_dir(State), - Apps = filename:join(Root, "apps"), - Lib = filename:join(Root, "lib"), - Deps = filename:join(Root, "deps"), - Ebin = filename:join(Root, "ebin"), - lists:foldl(fun(Dir, LibDirs) -> - case ec_file:exists(Dir) of - true -> - [erlang:iolist_to_binary(Dir) | LibDirs]; - false -> - LibDirs - end - end, [], [Deps, Lib, Apps, Ebin]) - end. - --spec add_system_lib_dir(rcl_state:t()) -> [file:name()]. -add_system_lib_dir(State) -> - ExcludeSystem = rcl_state:get(State, discover_exclude_system, false), - case ExcludeSystem of - true -> - []; - false -> - erlang:iolist_to_binary(code:lib_dir()) - end. - -add_release_output_dir(State) -> - case rcl_state:get(State, disable_discover_release_output, false) of - true -> - []; - false -> - Output = erlang:iolist_to_binary(rcl_state:output_dir(State)), - case ec_file:exists(erlang:binary_to_list(Output)) of - true -> - Output; - false -> - [] - end - end. diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl deleted file mode 100644 index 35e37bf..0000000 --- a/src/rcl_prv_overlay.erl +++ /dev/null @@ -1,418 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc Given a complete built release this provider assembles that release -%%% into a release directory. --module(rcl_prv_overlay). - --behaviour(rcl_provider). - --export([init/1, - do/1, - format_error/1]). - --define(DIRECTORY_RE, ".*(\/|\\\\)$"). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. -init(State) -> - {ok, State}. - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). -do(State) -> - {RelName, RelVsn} = rcl_state:default_configured_release(State), - Release = rcl_state:get_realized_release(State, RelName, RelVsn), - case rcl_release:realized(Release) of - true -> - generate_overlay_vars(State, Release); - false -> - ?RCL_ERROR({unresolved_release, RelName, RelVsn}) - end. - --spec format_error(ErrorDetail::term()) -> iolist(). -format_error({unresolved_release, RelName, RelVsn}) -> - io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); -format_error({ec_file_error, AppDir, TargetDir, E}) -> - io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", - [AppDir, TargetDir, E]); -format_error({unable_to_read_varsfile, FileName, Reason}) -> - io_lib:format("Unable to read vars file (~s) for overlay due to: ~p", - [FileName, Reason]); -format_error({overlay_failed, Errors}) -> - [[format_error(rcl_util:error_reason(Error)), "\n"] || Error <- Errors]; -format_error({dir_render_failed, Dir, Error}) -> - io_lib:format("rendering mkdir path failed ~s with ~p", - [Dir, Error]); -format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> - io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", - [AppDir, TargetDir, rcl_util:indent(1), - file:format_error(Reason)]); -format_error({copy_failed, FromFile, ToFile, Err}) -> - io_lib:format("Unable to copy from ~s to ~s because of ~p", - [FromFile, ToFile, Err]); -format_error({unable_to_write, ToFile, Reason}) -> - io_lib:format("Unable to write to ~s because ~p", - [ToFile, Reason]); -format_error({unable_to_enclosing_dir, ToFile, Reason}) -> - io_lib:format("Unable to create enclosing directory for ~s because ~p", - [ToFile, Reason]); -format_error({unable_to_render_template, FromFile, Reason}) -> - io_lib:format("Unable to render template ~s because ~p", - [FromFile, Reason]); -format_error({unable_to_compile_template, FromFile, Reason}) -> - io_lib:format("Unable to compile template ~s because ~p", - [FromFile, Reason]); -format_error({unable_to_make_dir, Absolute, Error}) -> - io_lib:format("Unable to make directory ~s because ~p", - [Absolute, Error]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec generate_overlay_vars(rcl_state:t(), rcl_release:t()) -> - {ok, rcl_state:t()} | relcool:error(). -generate_overlay_vars(State, Release) -> - StateVars = generate_state_vars(State), - ReleaseVars = generate_release_vars(Release), - get_overlay_vars_from_file(State, StateVars ++ ReleaseVars). - --spec get_overlay_vars_from_file(rcl_state:t(), proplists:proplist()) -> - {ok, rcl_state:t()} | relcool:error(). -get_overlay_vars_from_file(State, OverlayVars) -> - case rcl_state:get(State, overlay_vars, undefined) of - undefined -> - do_overlay(State, OverlayVars); - FileName -> - read_overlay_vars(State, OverlayVars, FileName) - end. - --spec read_overlay_vars(rcl_state:t(), proplists:proplist(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). -read_overlay_vars(State, OverlayVars, FileName) -> - RelativeRoot = get_relative_root(State), - RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)), - case file:consult(RelativePath) of - {ok, Terms} -> - case render_overlay_vars(OverlayVars, Terms, []) of - {ok, NewTerms} -> - do_overlay(State, NewTerms ++ OverlayVars); - Error -> - Error - end; - {error, Reason} -> - ?RCL_ERROR({unable_to_read_varsfile, FileName, Reason}) - end. - --spec render_overlay_vars(proplists:proplist(), proplists:proplist(), - proplists:proplist()) -> - {ok, proplists:proplist()} | relcool:error(). -render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) - when erlang:is_list(Value) -> - case io_lib:printable_list(Value) of - true -> - case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of - {ok, Data} -> - %% Adding to the end sucks, but ordering needs to be retained - render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, Data}]); - Error -> - Error - end; - false -> - case render_overlay_vars(Acc ++ OverlayVars, Value, []) of - {ok, NewValue} -> - render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, NewValue}]); - Error -> - Error - end - end; -render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) - when erlang:is_binary(Value) -> - case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of - {ok, Data} -> - render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, erlang:iolist_to_binary(Data)}]); - Error -> - Error - end; -render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) -> - render_overlay_vars(OverlayVars, Rest, Acc ++ [KeyValue]); -render_overlay_vars(_OverlayVars, [], Acc) -> - {ok, Acc}. - --spec generate_release_vars(rcl_release:t()) -> proplists:proplist(). -generate_release_vars(Release) -> - [{erts_vsn, rcl_release:erts(Release)}, - {release_erts_version, rcl_release:erts(Release)}, - {release_name, rcl_release:name(Release)}, - {rel_vsn, rcl_release:vsn(Release)}, - {release_version, rcl_release:vsn(Release)}, - {release_applications, lists:map(fun(App) -> - rcl_app_info:name(App) - end, rcl_release:application_details(Release))}, - {release, [generate_app_vars(App)|| App <- rcl_release:application_details(Release)]}, - {release_goals, [if - erlang:is_list(Constraint) -> - Constraint; - true -> - rcl_depsolver:format_constraint(Constraint) - end || Constraint <- rcl_release:goals(Release)]}]. - --spec generate_app_vars(rcl_app_info:t()) -> AppInfo::tuple(). -generate_app_vars(App) -> - {rcl_app_info:name(App), - [{version, rcl_app_info:vsn_as_string(App)}, - {dir, rcl_app_info:dir(App)}, - {active_dependencies, rcl_app_info:active_deps(App)}, - {library_dependencies, rcl_app_info:library_deps(App)}, - {link, rcl_app_info:link(App)}]}. - --spec generate_state_vars(rcl_state:t()) -> proplists:proplist(). -generate_state_vars(State) -> - [{log, rcl_log:format(rcl_state:log(State))}, - {output_dir, rcl_state:output_dir(State)}, - {target_dir, rcl_state:output_dir(State)}, - {overridden, [AppName || {AppName, _} <- rcl_state:overrides(State)]}, - {overrides, rcl_state:overrides(State)}, - {goals, [rcl_depsolver:format_constraint(Constraint) || - Constraint <- rcl_state:goals(State)]}, - {lib_dirs, rcl_state:lib_dirs(State)}, - {config_file, rcl_state:config_file(State)}, - {providers, rcl_state:providers(State)}, - {sys_config, rcl_state:sys_config(State)}, - {root_dir, rcl_state:root_dir(State)}, - {default_release_name, case rcl_state:default_configured_release(State) of - {Name0, _} -> - Name0 - end}, - {default_release_version, case rcl_state:default_configured_release(State) of - {_, Vsn0} -> - Vsn0 - end}, - {default_release, case rcl_state:default_configured_release(State) of - {Name1, undefined} -> - erlang:atom_to_list(Name1); - {Name1, Vsn1} -> - erlang:atom_to_list(Name1) ++ "-" ++ Vsn1 - end}]. - --spec do_overlay(rcl_state:t(), proplists:proplist()) -> - {ok, rcl_state:t()} | relcool:error(). -do_overlay(State, OverlayVars) -> - case rcl_state:get(State, overlay, undefined) of - undefined -> - {ok, State}; - Overlays -> - handle_errors(State, - lists:map(fun(Overlay) -> - do_individual_overlay(State, OverlayVars, - Overlay) - end, Overlays)) - end. - --spec handle_errors(rcl_state:t(), [ok | relcool:error()]) -> - {ok, rcl_state:t()} | relcool:error(). -handle_errors(State, Result) -> - case [Error || Error <- Result, - rcl_util:is_error(Error)] of - Errors = [_|_] -> - ?RCL_ERROR({overlay_failed, Errors}); - [] -> - {ok, State} - end. - --spec do_individual_overlay(rcl_state:t(), proplists:proplist(), - OverlayDirective::term()) -> - {ok, rcl_state:t()} | relcool:error(). -do_individual_overlay(State, OverlayVars, {mkdir, Dir}) -> - ModuleName = make_template_name("rcl_mkdir_template", Dir), - case erlydtl:compile(erlang:iolist_to_binary(Dir), ModuleName) of - {ok, ModuleName} -> - case render(ModuleName, OverlayVars) of - {ok, IoList} -> - Absolute = absolutize(State, - filename:join(rcl_state:output_dir(State), - erlang:iolist_to_binary(IoList))), - case rcl_util:mkdir_p(Absolute) of - {error, Error} -> - ?RCL_ERROR({unable_to_make_dir, Absolute, Error}); - ok -> - ok - end; - {error, Error} -> - ?RCL_ERROR({dir_render_failed, Dir, Error}) - end; - {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Dir, Reason}) - end; -do_individual_overlay(State, OverlayVars, {copy, From, To}) -> - FromTemplateName = make_template_name("rcl_copy_from_template", From), - ToTemplateName = make_template_name("rcl_copy_to_template", To), - file_render_do(OverlayVars, From, FromTemplateName, - fun(FromFile) -> - file_render_do(OverlayVars, To, ToTemplateName, - fun(ToFile) -> - copy_to(State, FromFile, ToFile) - end) - end); -do_individual_overlay(State, OverlayVars, {template, From, To}) -> - FromTemplateName = make_template_name("rcl_template_from_template", From), - ToTemplateName = make_template_name("rcl_template_to_template", To), - file_render_do(OverlayVars, From, FromTemplateName, - fun(FromFile) -> - file_render_do(OverlayVars, To, ToTemplateName, - fun(ToFile) -> - RelativeRoot = get_relative_root(State), - FromFile0 = absolutize(State, - filename:join(RelativeRoot, - erlang:iolist_to_binary(FromFile))), - FromFile1 = erlang:binary_to_list(FromFile0), - write_template(OverlayVars, - FromFile1, - absolutize(State, - filename:join(rcl_state:output_dir(State), - erlang:iolist_to_binary(ToFile)))) - end) - end). - --spec copy_to(rcl_state:t(), file:name(), file:name()) -> ok | relcool:error(). -copy_to(State, FromFile0, ToFile0) -> - RelativeRoot = get_relative_root(State), - ToFile1 = absolutize(State, filename:join(rcl_state:output_dir(State), - erlang:iolist_to_binary(ToFile0))), - - FromFile1 = absolutize(State, filename:join(RelativeRoot, - erlang:iolist_to_binary(FromFile0))), - ToFile2 = case is_directory(ToFile0, ToFile1) of - false -> - filelib:ensure_dir(ToFile1), - ToFile1; - true -> - rcl_util:mkdir_p(ToFile1), - erlang:iolist_to_binary(filename:join(ToFile1, - filename:basename(FromFile1))) - end, - case ec_file:copy(FromFile1, ToFile2) of - ok -> - {ok, FileInfo} = file:read_file_info(FromFile1), - ok = file:write_file_info(ToFile2, FileInfo), - ok; - {error, Err} -> - ?RCL_ERROR({copy_failed, - FromFile1, - ToFile1, Err}) - end. - -get_relative_root(State) -> - case rcl_state:config_file(State) of - [] -> - rcl_state:root_dir(State); - Config -> - filename:dirname(Config) - end. - --spec is_directory(file:name(), file:name()) -> boolean(). -is_directory(ToFile0, ToFile1) -> - case re:run(ToFile0, ?DIRECTORY_RE) of - nomatch -> - filelib:is_dir(ToFile1); - _ -> - true - end. - - --spec render_template(proplists:proplist(), iolist()) -> - ok | relcool:error(). -render_template(OverlayVars, Data) -> - TemplateName = make_template_name("rcl_template_renderer", Data), - case erlydtl:compile(Data, TemplateName) of - Good when Good =:= ok; Good =:= {ok, TemplateName} -> - case render(TemplateName, OverlayVars) of - {ok, IoData} -> - {ok, IoData}; - {error, Reason} -> - ?RCL_ERROR({unable_to_render_template, Data, Reason}) - end; - {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Data, Reason}) - end. - -write_template(OverlayVars, FromFile, ToFile) -> - case render_template(OverlayVars, FromFile) of - {ok, IoData} -> - case filelib:ensure_dir(ToFile) of - ok -> - case file:write_file(ToFile, IoData) of - ok -> - {ok, FileInfo} = file:read_file_info(FromFile), - ok = file:write_file_info(ToFile, FileInfo), - ok; - {error, Reason} -> - ?RCL_ERROR({unable_to_write, ToFile, Reason}) - end; - {error, Reason} -> - ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason}) - end; - Error -> - Error - end. - --spec file_render_do(proplists:proplist(), iolist(), module(), - fun((term()) -> {ok, rcl_state:t()} | relcool:error())) -> - {ok, rcl_state:t()} | relcool:error(). -file_render_do(OverlayVars, Data, TemplateName, NextAction) -> - case erlydtl:compile(erlang:iolist_to_binary(Data), TemplateName) of - {ok, TemplateName} -> - case render(TemplateName, OverlayVars) of - {ok, IoList} -> - NextAction(IoList); - {error, Error} -> - ?RCL_ERROR({render_failed, Data, Error}) - end; - {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Data, Reason}) - end. - --spec make_template_name(string(), term()) -> module(). -make_template_name(Base, Value) -> - %% Seed so we get different values each time - random:seed(erlang:now()), - Hash = erlang:phash2(Value), - Ran = random:uniform(10000000), - erlang:list_to_atom(Base ++ "_" ++ - erlang:integer_to_list(Hash) ++ - "_" ++ erlang:integer_to_list(Ran)). - --spec render(module(), proplists:proplist()) -> {ok, iolist()} | {error, Reason::term()}. -render(ModuleName, OverlayVars) -> - try - ModuleName:render(OverlayVars) - catch - _:Reason -> - {error, Reason} - end. - -absolutize(State, FileName) -> - filename:absname(filename:join(rcl_state:root_dir(State), - erlang:iolist_to_binary(FileName))). diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl deleted file mode 100644 index d90b492..0000000 --- a/src/rcl_prv_release.erl +++ /dev/null @@ -1,187 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provider uses the lib_dir setting of the state. It searches the -%%% Lib Dirs looking for all OTP Applications that are available. When it finds -%%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_prv_release). - --behaviour(rcl_provider). - --export([init/1, - do/1, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. -init(State) -> - {ok, State}. - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). -do(State) -> - DepGraph = create_dep_graph(State), - find_default_release(State, DepGraph). - --spec format_error(ErrorDetail::term()) -> iolist(). -format_error(no_goals_specified) -> - "No goals specified for this release ~n"; -format_error({no_release_name, Vsn}) -> - io_lib:format("A target release version was specified (~s) but no name", [Vsn]); -format_error({invalid_release_info, Info}) -> - io_lib:format("Target release information is in an invalid format ~p", [Info]); -format_error({multiple_release_names, RelA, RelB}) -> - io_lib:format("No default release name was specified and there are multiple " - "releases in the config: ~s, ~s", - [RelA, RelB]); -format_error(no_releases_in_system) -> - "No releases have been specified in the system!"; -format_error({no_releases_for, RelName}) -> - io_lib:format("No releases exist in the system for ~s!", [RelName]); -format_error({release_not_found, {RelName, RelVsn}}) -> - io_lib:format("No releases exist in the system for ~p:~s!", [RelName, RelVsn]); -format_error({failed_solve, Error}) -> - io_lib:format("Failed to solve release:\n ~s", - [rcl_depsolver:format_error({error, Error})]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec create_dep_graph(rcl_state:t()) -> rcl_depsolver:t(). -create_dep_graph(State) -> - Apps = rcl_state:available_apps(State), - Graph0 = rcl_depsolver:new_graph(), - lists:foldl(fun(App, Graph1) -> - AppName = rcl_app_info:name(App), - AppVsn = rcl_app_info:vsn(App), - Deps = rcl_app_info:active_deps(App) ++ - rcl_app_info:library_deps(App), - rcl_depsolver:add_package_version(Graph1, - AppName, - AppVsn, - Deps) - end, Graph0, Apps). - - --spec find_default_release(rcl_state:t(), rcl_depsolver:t()) -> - {ok, rcl_state:t()} | relcool:error(). -find_default_release(State, DepGraph) -> - case rcl_state:default_configured_release(State) of - {undefined, undefined} -> - resolve_default_release(State, DepGraph); - {RelName, undefined} -> - resolve_default_version(State, DepGraph, RelName); - {undefined, Vsn} -> - ?RCL_ERROR({no_release_name, Vsn}); - {RelName, RelVsn} -> - solve_release(State, DepGraph, RelName, RelVsn) - end. - -resolve_default_release(State0, DepGraph) -> - %% Here we will just get the highest versioned release and run that. - case lists:sort(fun release_sort/2, - ec_dictionary:to_list(rcl_state:configured_releases(State0))) of - [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), - solve_release(State1, DepGraph, RelName, RelVsn); - [] -> - ?RCL_ERROR(no_releases_in_system) - end. - -resolve_default_version(State0, DepGraph, RelName) -> - %% Here we will just get the lastest version and run that. - AllReleases = ec_dictionary:to_list(rcl_state:configured_releases(State0)), - SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases, - PossibleRelName =:= RelName], - case lists:sort(fun release_sort/2, SpecificReleases) of - [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), - solve_release(State1, DepGraph, RelName, RelVsn); - [] -> - ?RCL_ERROR({no_releases_for, RelName}) - end. - --spec release_sort({{rcl_release:name(),rcl_release:vsn()}, term()}, - {{rcl_release:name(),rcl_release:vsn()}, term()}) -> - boolean(). -release_sort({{RelName, RelVsnA}, _}, - {{RelName, RelVsnB}, _}) -> - ec_semver:lte(RelVsnA, RelVsnB); -release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) -> - %% The release names are different. When the releases are named differently - %% we can not just take the lastest version. You *must* provide a default - %% release name at least. So we throw an error here that the top can catch - %% and return - erlang:atom_to_list(RelNameA) =< erlang:atom_to_list(RelNameB) andalso - ec_semver:lte(RelVsnA, RelVsnB). - -solve_release(State0, DepGraph, RelName, RelVsn) -> - rcl_log:debug(rcl_state:log(State0), - "Solving Release ~p-~s~n", - [RelName, RelVsn]), - try - Release = rcl_state:get_configured_release(State0, RelName, RelVsn), - Goals = rcl_release:goals(Release), - case Goals of - [] -> - ?RCL_ERROR(no_goals_specified); - _ -> - case rcl_depsolver:solve(DepGraph, Goals) of - {ok, Pkgs} -> - set_resolved(State0, Release, Pkgs); - {error, Error} -> - ?RCL_ERROR({failed_solve, Error}) - end - end - catch - throw:not_found -> - ?RCL_ERROR({release_not_found, RelName, RelVsn}) - end. - -set_resolved(State, Release0, Pkgs) -> - case rcl_release:realize(Release0, Pkgs, rcl_state:available_apps(State)) of - {ok, Release1} -> - rcl_log:info(rcl_state:log(State), - "Resolved ~p-~s~n", - [rcl_release:name(Release1), - rcl_release:vsn(Release1)]), - rcl_log:debug(rcl_state:log(State), - fun() -> - rcl_release:format(1, Release1) - end), - {ok, rcl_state:add_realized_release(State, Release1)}; - {error, E} -> - ?RCL_ERROR({release_error, E}) - end. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - --endif. diff --git a/src/rcl_rel_discovery.erl b/src/rcl_rel_discovery.erl deleted file mode 100644 index 6cd84f0..0000000 --- a/src/rcl_rel_discovery.erl +++ /dev/null @@ -1,156 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This provider uses the lib_dir setting of the state. It searches the -%%% Lib Dirs looking for all OTP Applications that are available. When it finds -%%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_rel_discovery). - --export([do/3, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% API -%%============================================================================ - -%% @doc recursively dig down into the library directories specified in the state -%% looking for OTP Applications --spec do(rcl_state:t(), [filename:name()], [rcl_app_info:t()]) -> - {ok, [rcl_release:t()]} | relcool:error(). -do(State, LibDirs, AppMeta) -> - rcl_log:info(rcl_state:log(State), - fun() -> - ["Resolving available releases from directories:\n", - [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] - end), - resolve_rel_metadata(State, LibDirs, AppMeta). - --spec format_error([ErrorDetail::term()]) -> iolist(). -format_error(ErrorDetails) - when erlang:is_list(ErrorDetails) -> - [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== -resolve_rel_metadata(State, LibDirs, AppMeta) -> - ReleaseMeta0 = lists:flatten(rcl_dscv_util:do(fun(LibDir, FileType) -> - discover_dir(LibDir, - AppMeta, - FileType) - end, LibDirs)), - - Errors = [case El of - {error, Ret} -> Ret; - _ -> El - end - || El <- ReleaseMeta0, - case El of - {error, _} -> - true; - _ -> - false - end], - - case Errors of - [] -> - ReleaseMeta1 = [RelMeta || {ok, RelMeta} <- ReleaseMeta0], - rcl_log:debug(rcl_state:log(State), - fun() -> - ["Resolved the following OTP Releases from the system: \n", - [[rcl_release:format(1, Rel), "\n"] || Rel <- ReleaseMeta1]] - end), - {ok, ReleaseMeta1}; - _ -> - ?RCL_ERROR(Errors) - end. - --spec format_detail(ErrorDetail::term()) -> iolist(). -format_detail({accessing, File, eaccess}) -> - io_lib:format("permission denied accessing file ~s", [File]); -format_detail({accessing, File, Type}) -> - io_lib:format("error (~p) accessing file ~s", [Type, File]). - --spec discover_dir(file:name(), [rcl_app_info:t()], directory | file) -> - {ok, rcl_release:t()} - | {error, Reason::term()} - | {noresult, false}. -discover_dir(_File, _AppMeta, directory) -> - {noresult, true}; -discover_dir(File, AppMeta, file) -> - is_valid_release(File, AppMeta). - --spec is_valid_release(file:name(), - [rcl_app_info:t()]) -> - {ok, rcl_release:t()} - | {error, Reason::term()} - | {noresult, false}. -is_valid_release(File, AppMeta) -> - case filename:extension(File) of - <<".rel">>-> - resolve_release(File, AppMeta); - _ -> - {noresult, false} - end. - -resolve_release(RelFile, AppMeta) -> - case file:consult(RelFile) of - {ok, [{release, {RelName, RelVsn}, - {erts, ErtsVsn}, - Apps}]} -> - build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta); - {ok, InvalidRelease} -> - ?RCL_ERROR({invalid_release_information, InvalidRelease}); - {error, Reason} -> - ?RCL_ERROR({unable_to_read, RelFile, Reason}) - end. - -build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> - Release = rcl_release:erts(rcl_release:new(RelName, RelVsn, RelFile), - ErtsVsn), - resolve_apps(Apps, AppMeta, Release, []). - -resolve_apps([], _AppMeta, Release, Acc) -> - {ok, rcl_release:application_details(Release, Acc)}; -resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> - AppName = erlang:element(1, AppInfo), - AppVsn = ec_semver:parse(erlang:element(2, AppInfo)), - case find_app(AppName, AppVsn, AppMeta) of - Error = {error, _} -> - Error; - ResolvedApp -> - resolve_apps(Apps, AppMeta, Release, [ResolvedApp | Acc]) - end. - -find_app(AppName, AppVsn, AppMeta) -> - case ec_lists:find(fun(App) -> - NAppName = rcl_app_info:name(App), - NAppVsn = rcl_app_info:vsn(App), - AppName == NAppName andalso - AppVsn == NAppVsn - end, AppMeta) of - {ok, Head} -> - Head; - error -> - ?RCL_ERROR({could_not_find, {AppName, AppVsn}}) - end. diff --git a/src/rcl_release.erl b/src/rcl_release.erl deleted file mode 100644 index 9ed741e..0000000 --- a/src/rcl_release.erl +++ /dev/null @@ -1,415 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc This module represents a release and its metadata and is used to -%%% manipulate the release metadata. --module(rcl_release). - --export([new/2, - new/3, - relfile/1, - relfile/2, - erts/2, - erts/1, - goals/2, - goals/1, - name/1, - vsn/1, - realize/3, - applications/1, - application_details/1, - application_details/2, - realized/1, - metadata/1, - canonical_name/1, - format/1, - format/2, - format_error/1]). - --export_type([t/0, - name/0, - vsn/0, - app_name/0, - app_vsn/0, - app_type/0, - application_spec/0, - application_goal/0]). - --include_lib("relcool/include/relcool.hrl"). - --record(release_t, {name :: atom(), - vsn :: ec_semver:any_version(), - erts :: ec_semver:any_version(), - goals = [] :: [rcl_depsolver:constraint()], - realized = false :: boolean(), - annotations = undefined :: annotations(), - applications = [] :: [application_spec()], - relfile :: undefined | string(), - app_detail = [] :: [rcl_app_info:t()]}). - -%%============================================================================ -%% types -%%============================================================================ --type name() :: atom(). --type vsn() :: string(). --type app_name() :: atom(). --type app_vsn() :: string(). --type app_type() :: permanent | transient | temporary | load | none. --type incl_apps() :: [app_name()]. - --type application_spec() :: {app_name(), app_vsn()} | - {app_name(), app_vsn(), app_type() | incl_apps()} | - {app_name(), app_vsn(), app_type(), incl_apps()}. - --type application_constraint() :: rcl_depsolver:raw_constraint() | string() | binary(). --type application_goal() :: application_constraint() - | {application_constraint(), app_type() | incl_apps()} - | {application_constraint(), app_type(), incl_apps() | none}. - --type annotations() :: ec_dictionary:dictionary(app_name(), - {app_type(), incl_apps() | none}). - - --opaque t() :: record(release_t). - -%%============================================================================ -%% API -%%============================================================================ --spec new(atom(), string(), undefined | file:name()) -> t(). -new(ReleaseName, ReleaseVsn, Relfile) -> - #release_t{name=to_atom(ReleaseName), vsn=ReleaseVsn, - relfile = Relfile, - annotations=ec_dictionary:new(ec_dict)}. - --spec new(atom(), string()) -> t(). -new(ReleaseName, ReleaseVsn) -> - new(ReleaseName, ReleaseVsn, undefined). - - --spec relfile(t()) -> file:name() | undefined. -relfile(#release_t{relfile=Relfile}) -> - Relfile. - --spec relfile(t(), file:name()) -> t(). -relfile(Release, Relfile) -> - Release#release_t{relfile=Relfile}. - --spec name(t()) -> atom(). -name(#release_t{name=Name}) -> - Name. - --spec vsn(t()) -> string(). -vsn(#release_t{vsn=Vsn}) -> - Vsn. - --spec erts(t(), app_vsn()) -> t(). -erts(Release, Vsn) -> - Release#release_t{erts=Vsn}. - --spec erts(t()) -> app_vsn(). -erts(#release_t{erts=Vsn}) -> - Vsn. - --spec goals(t(), [application_goal()]) -> {ok, t()} | relcool:error(). -goals(Release, Goals0) -> - lists:foldl(fun parse_goal0/2, - {ok, Release}, Goals0). - --spec goals(t()) -> [application_goal()]. -goals(#release_t{goals=Goals}) -> - Goals. - --spec realize(t(), [{app_name(), app_vsn()}], [rcl_app_info:t()]) -> - {ok, t()} | relcool:error(). -realize(Rel, Pkgs0, World0) -> - World1 = subset_world(Pkgs0, World0), - case rcl_topo:sort_apps(World1) of - {ok, Pkgs1} -> - process_specs(realize_erts(Rel), Pkgs1); - Error={error, _} -> - Error - end. - -%% @doc this gives the application specs for the release. This can only be -%% populated by the 'realize' call in this module. --spec applications(t()) -> [application_spec()]. -applications(#release_t{applications=Apps}) -> - Apps. - -%% @doc this gives the rcl_app_info objects representing the applications in -%% this release. These should only be populated by the 'realize' call in this -%% module or by reading an existing rel file. --spec application_details(t()) -> [rcl_app_info:t()]. -application_details(#release_t{app_detail=App}) -> - App. - -%% @doc this is only expected to be called by a process building a new release -%% from an existing rel file. --spec application_details(t(), [rcl_app_info:t()]) -> t(). -application_details(Release, AppDetail) -> - Release#release_t{app_detail=AppDetail}. - --spec realized(t()) -> boolean(). -realized(#release_t{realized=Realized}) -> - Realized. - --spec metadata(t()) -> term(). -metadata(#release_t{name=Name, vsn=Vsn, erts=ErtsVsn, applications=Apps, - realized=Realized}) -> - case Realized of - true -> - {ok, {release, {erlang:atom_to_list(Name), Vsn}, {erts, ErtsVsn}, - Apps}}; - false -> - ?RCL_ERROR({not_realized, Name, Vsn}) - end. - -%% @doc produce the canonical name (-) for this release --spec canonical_name(t()) -> string(). -canonical_name(#release_t{name=Name, vsn=Vsn}) -> - erlang:binary_to_list(erlang:iolist_to_binary([erlang:atom_to_list(Name), "-", - Vsn])). - --spec format(t()) -> iolist(). -format(Release) -> - format(0, Release). - --spec format(non_neg_integer(), t()) -> iolist(). -format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, - goals = Goals, applications=Apps}) -> - BaseIndent = rcl_util:indent(Indent), - [BaseIndent, "release: ", rcl_util:to_string(Name), "-", Vsn, "\n", - rcl_util:indent(Indent + 1), " erts-", ErtsVsn, - ", realized = ", erlang:atom_to_list(Realized), "\n", - BaseIndent, "goals: \n", - [[rcl_util:indent(Indent + 1), format_goal(Goal), ",\n"] || Goal <- Goals], - case Realized of - true -> - [BaseIndent, "applications: \n", - [[rcl_util:indent(Indent + 1), io_lib:format("~p", [App]), ",\n"] || - App <- Apps]]; - false -> - [] - end]. - --spec format_goal(application_goal()) -> iolist(). -format_goal({Constraint, AppType}) -> - io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType}]); -format_goal({Constraint, AppType, AppInc}) -> - io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType, AppInc}]); -format_goal(Constraint) -> - rcl_depsolver:format_constraint(Constraint). - --spec format_error(Reason::term()) -> iolist(). -format_error({topo_error, E}) -> - rcl_topo:format_error(E); -format_error({failed_to_parse, Con}) -> - io_lib:format("Failed to parse constraint ~p", [Con]); -format_error({invalid_constraint, _, Con}) -> - io_lib:format("Invalid constraint specified ~p", [Con]); -format_error({not_realized, Name, Vsn}) -> - io_lib:format("Unable to produce metadata release: ~p-~s has not been realized", - [Name, Vsn]). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== --spec realize_erts(t()) -> t(). -realize_erts(Rel=#release_t{erts=undefined}) -> - Rel#release_t{erts=erlang:system_info(version)}; -realize_erts(Rel) -> - Rel. - --spec process_specs(t(), [rcl_app_info:t()]) -> - {ok, t()}. -process_specs(Rel=#release_t{annotations=Annots, - goals=Goals}, World) -> - ActiveApps = lists:flatten([rcl_app_info:active_deps(El) || El <- World] ++ - [case get_app_name(Goal) of - {error, _} -> []; - G -> G - end || Goal <- Goals]), - LibraryApps = lists:flatten([rcl_app_info:library_deps(El) || El <- World]), - Specs = [create_app_spec(Annots, App, ActiveApps, LibraryApps) || App <- World], - {ok, Rel#release_t{annotations=Annots, - applications=Specs, - app_detail=World, - realized=true}}. - --spec create_app_spec(annotations(), rcl_app_info:t(), [app_name()], - [app_name()]) -> - application_spec(). -create_app_spec(Annots, App, ActiveApps, LibraryApps) -> - %% If the app only exists as a dependency in a library app then it should - %% get the 'load' annotation unless the release spec has provided something - %% else - AppName = rcl_app_info:name(App), - TypeAnnot = - case (lists:member(AppName, LibraryApps) and - (not lists:member(AppName, ActiveApps))) of - true -> - load; - false -> - none - end, - BaseAnnots = - try - case ec_dictionary:get(AppName, Annots) of - {none, Incld} -> - {TypeAnnot, Incld}; - Else -> - Else - end - catch - throw:not_found -> - {TypeAnnot, none} - end, - Vsn = rcl_app_info:vsn_as_string(App), - case BaseAnnots of - {none, none} -> - {AppName, Vsn}; - {Type, none} -> - {AppName, Vsn, Type}; - {none, Incld0} -> - {AppName, Vsn, Incld0}; - {Type, Incld1} -> - {AppName, Vsn, Type, Incld1} - end. - --spec subset_world([{app_name(), app_vsn()}], [rcl_app_info:t()]) -> [rcl_app_info:t()]. -subset_world(Pkgs, World) -> - [get_app_info(Pkg, World) || Pkg <- Pkgs]. - --spec get_app_info({app_name(), app_vsn()}, [rcl_app_info:t()]) -> rcl_app_info:t(). -get_app_info({PkgName, PkgVsn}, World) -> - {ok, WorldEl} = - ec_lists:find(fun(El) -> - rcl_app_info:name(El) =:= PkgName andalso - rcl_app_info:vsn(El) =:= PkgVsn - end, World), - WorldEl. - -parse_goal0({Constraint0, Annots}, {ok, Release}) - when Annots =:= permanent; - Annots =:= transient; - Annots =:= temporary; - Annots =:= load; - Annots =:= none -> - case parse_constraint(Constraint0) of - {ok, Constraint1} -> - parse_goal1(Release, Constraint1, {Annots, none}); - Error -> - Error - end; -parse_goal0({Constraint0, Annots, Incls}, {ok, Release}) - when (Annots =:= permanent orelse - Annots =:= transient orelse - Annots =:= temporary orelse - Annots =:= load orelse - Annots =:= none), - erlang:is_list(Incls) -> - case parse_constraint(Constraint0) of - {ok, Constraint1} -> - parse_goal1(Release, Constraint1, {Annots, Incls}); - Error -> - Error - end; -parse_goal0(Constraint0, {ok, Release}) -> - case parse_constraint(Constraint0) of - {ok, Constraint1} -> - parse_goal1(Release, Constraint1, {none, none}); - Error -> - Error - end; -parse_goal0(_, E = {error, _}) -> - E; -parse_goal0(Constraint, _) -> - ?RCL_ERROR({invalid_constraint, 1, Constraint}). - -parse_goal1(Release = #release_t{annotations=Annots, goals=Goals}, - Constraint, NewAnnots) -> - case get_app_name(Constraint) of - E1 = {error, _} -> - E1; - AppName -> - {ok, - Release#release_t{annotations=ec_dictionary:add(AppName, NewAnnots, Annots), - goals = [Constraint | Goals]}} - end. - --spec parse_constraint(application_constraint()) -> - rcl_depsolver:constraint() | relcool:error(). -parse_constraint(Constraint0) - when erlang:is_list(Constraint0); erlang:is_binary(Constraint0) -> - case rcl_goal:parse(Constraint0) of - {fail, _} -> - ?RCL_ERROR({failed_to_parse, Constraint0}); - {ok, Constraint1} -> - {ok, Constraint1} - end; -parse_constraint(Constraint0) - when erlang:is_tuple(Constraint0); - erlang:is_atom(Constraint0) -> - Constraint1 = parse_version(Constraint0), - case rcl_depsolver:is_valid_constraint(Constraint1) of - false -> - ?RCL_ERROR({invalid_constraint, 2, Constraint0}); - true -> - {ok, Constraint1} - end; -parse_constraint(Constraint) -> - ?RCL_ERROR({invalid_constraint, 3, Constraint}). - --spec get_app_name(rcl_depsolver:raw_constraint()) -> - AppName::atom() | relcool:error(). -get_app_name(AppName) when erlang:is_atom(AppName) -> - AppName; -get_app_name({AppName, _}) when erlang:is_atom(AppName) -> - AppName; -get_app_name({AppName, _, _}) when erlang:is_atom(AppName) -> - AppName; -get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) -> - AppName; -get_app_name(V) -> - ?RCL_ERROR({invalid_constraint, 4, V}). - --spec parse_version(rcl_depsolver:raw_constraint()) -> - rcl_depsolver:constraint(). -parse_version({AppName, Version}) - when erlang:is_binary(Version); - erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version)}; -parse_version({AppName, Version, Constraint}) - when erlang:is_binary(Version); - erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version), Constraint}; -parse_version({AppName, Version, Constraint0, Constraint1}) - when erlang:is_binary(Version); - erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version), Constraint1, Constraint0}; -parse_version(Constraint) -> - Constraint. - -to_atom(RelName) - when erlang:is_list(RelName) -> - erlang:list_to_atom(RelName); -to_atom(Else) - when erlang:is_atom(Else) -> - Else. diff --git a/src/rcl_state.erl b/src/rcl_state.erl deleted file mode 100644 index ca6ec8c..0000000 --- a/src/rcl_state.erl +++ /dev/null @@ -1,354 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc Provides state management services for the relcool tool. Generally, -%%% those things that are fixed have a direct api. Those things that are mutable -%%% have a more mutable api. --module(rcl_state). - --export([new/2, - log/1, - action/1, - output_dir/1, - lib_dirs/1, - overrides/1, - overrides/2, - skip_apps/1, - skip_apps/2, - goals/1, - config_file/1, - config_file/2, - providers/1, - providers/2, - sys_config/1, - sys_config/2, - root_dir/1, - root_dir/2, - add_configured_release/2, - get_configured_release/3, - configured_releases/1, - realized_releases/1, - realized_releases/2, - add_realized_release/2, - get_realized_release/3, - update_realized_release/2, - default_configured_release/1, - default_configured_release/3, - available_apps/1, - available_apps/2, - get/2, - get/3, - put/3, - caller/1, - caller/2, - upfrom/1, - format/1, - format/2]). - - --export_type([t/0, - releases/0, - cmd_args/0]). - --record(state_t, {log :: rcl_log:t(), - root_dir :: file:name(), - caller :: caller(), - action :: atom(), - output_dir :: file:name(), - lib_dirs=[] :: [file:name()], - config_file=[] :: file:filename() | undefined, - goals=[] :: [rcl_depsolver:constraint()], - providers = [] :: [rcl_provider:t()], - available_apps = [] :: [rcl_app_info:t()], - default_configured_release :: {rcl_release:name(), rcl_release:vsn()}, - sys_config :: file:filename() | undefined, - overrides :: [{AppName::atom(), Directory::file:filename()}], - skip_apps = [] :: [AppName::atom()], - configured_releases :: releases(), - realized_releases :: releases(), - upfrom :: string() | binary() | undefined, - config_values :: ec_dictionary:dictionary(Key::atom(), - Value::term())}). - -%%============================================================================ -%% types -%%============================================================================ - --type releases() :: ec_dictionary:dictionary({rcl_release:name(), - rcl_release:vsn()}, - rcl_release:t()). --type cmd_args() :: proplists:proplist(). --type caller() :: command_line | api. - --opaque t() :: record(state_t). - -%%============================================================================ -%% API -%%============================================================================ -%% @doc Create a new 'log level' for the system --spec new(proplists:proplist(), atom()) -> t(). -new(PropList, Target) - when erlang:is_list(PropList), - erlang:is_atom(Target) -> - {ok, Root} = file:get_cwd(), - State0 = - #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), - output_dir=proplists:get_value(output_dir, PropList, ""), - lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], - config_file=proplists:get_value(config, PropList, undefined), - action = Target, - caller = proplists:get_value(caller, PropList, api), - goals=proplists:get_value(goals, PropList, []), - providers = [], - configured_releases=ec_dictionary:new(ec_dict), - realized_releases=ec_dictionary:new(ec_dict), - config_values=ec_dictionary:new(ec_dict), - overrides = proplists:get_value(overrides, PropList, []), - root_dir = proplists:get_value(root_dir, PropList, Root), - upfrom = proplists:get_value(upfrom, PropList, undefined), - default_configured_release={proplists:get_value(relname, PropList, undefined), - proplists:get_value(relvsn, PropList, undefined)}}, - rcl_state:put(create_logic_providers(State0), - disable_default_libs, - proplists:get_value(disable_default_libs, PropList, false)). - -%% @doc the action targeted for this system --spec action(t()) -> atom(). -action(#state_t{action=Action}) -> - Action. - -%% @doc the application overrides for the system --spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}]. -overrides(#state_t{overrides=Overrides}) -> - Overrides. - -%% @doc the application overrides for the system --spec overrides(t(), [{AppName::atom(), Directory::file:filename()}]) -> t(). -overrides(State, Overrides) -> - State#state_t{overrides=Overrides}. - - --spec skip_apps(t()) -> [AppName::atom()]. -skip_apps(#state_t{skip_apps=Apps}) -> - Apps. - -%% @doc the application overrides for the system --spec skip_apps(t(), [AppName::atom()]) -> t(). -skip_apps(State, SkipApps) -> - State#state_t{skip_apps=SkipApps}. - -%% @doc get the current log state for the system --spec log(t()) -> rcl_log:t(). -log(#state_t{log=LogState}) -> - LogState. - --spec output_dir(t()) -> file:name(). -output_dir(#state_t{output_dir=OutDir}) -> - OutDir. - --spec lib_dirs(t()) -> [file:name()]. -lib_dirs(#state_t{lib_dirs=LibDir}) -> - LibDir. - --spec goals(t()) -> [rcl_depsolver:constraint()]. -goals(#state_t{goals=TS}) -> - TS. - --spec config_file(t()) -> file:filename() | undefined. -config_file(#state_t{config_file=ConfigFiles}) -> - ConfigFiles. - --spec config_file(t(), file:filename() | undefined) -> t(). -config_file(State, ConfigFiles) -> - State#state_t{config_file=ConfigFiles}. - --spec providers(t()) -> [rcl_provider:t()]. -providers(#state_t{providers=Providers}) -> - Providers. - --spec sys_config(t()) -> file:filename() | undefined. -sys_config(#state_t{sys_config=SysConfig}) -> - SysConfig. - --spec sys_config(t(), file:filename()) -> t(). -sys_config(State, SysConfig) -> - State#state_t{sys_config=SysConfig}. - --spec root_dir(t()) -> file:filename() | undefined. -root_dir(#state_t{root_dir=RootDir}) -> - RootDir. - --spec root_dir(t(), file:filename()) -> t(). -root_dir(State, RootDir) -> - State#state_t{root_dir=RootDir}. - --spec providers(t(), [rcl_provider:t()]) -> t(). -providers(M, NewProviders) -> - M#state_t{providers=NewProviders}. - --spec add_configured_release(t(), rcl_release:t()) -> t(). -add_configured_release(M=#state_t{configured_releases=Releases}, Release) -> - M#state_t{configured_releases=ec_dictionary:add({rcl_release:name(Release), - rcl_release:vsn(Release)}, - Release, - Releases)}. - --spec get_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). -get_configured_release(#state_t{configured_releases=Releases}, Name, Vsn) -> - ec_dictionary:get({Name, Vsn}, Releases). - --spec configured_releases(t()) -> releases(). -configured_releases(#state_t{configured_releases=Releases}) -> - Releases. - --spec realized_releases(t()) -> releases(). -realized_releases(#state_t{realized_releases=Releases}) -> - Releases. - --spec realized_releases(t(), releases()) -> t(). -realized_releases(State, Releases) -> - State#state_t{realized_releases=Releases}. - --spec add_realized_release(t(), rcl_release:t()) -> t(). -add_realized_release(State = #state_t{realized_releases=Releases}, Release) -> - NewReleases = ec_dictionary:add({rcl_release:name(Release), rcl_release:vsn(Release)}, - Release, Releases), - State#state_t{realized_releases=NewReleases}. - --spec get_realized_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). -get_realized_release(#state_t{realized_releases=Releases}, Name, Vsn) -> - ec_dictionary:get({Name, Vsn}, Releases). - --spec update_realized_release(t(), rcl_release:t()) -> - t(). -update_realized_release(M=#state_t{realized_releases=Releases}, Release) -> - M#state_t{realized_releases=ec_dictionary:add({rcl_release:name(Release), - rcl_release:vsn(Release)}, - Release, - Releases)}. - --spec default_configured_release(t()) -> - {rcl_release:name() | undefined, rcl_release:vsn() | undefined}. -default_configured_release(#state_t{default_configured_release=Def}) -> - Def. - --spec default_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). -default_configured_release(M, Name, Vsn) -> - M#state_t{default_configured_release={Name, Vsn}}. - --spec available_apps(t()) -> [rcl_app_info:t()]. -available_apps(#state_t{available_apps=Apps}) -> - Apps. - --spec available_apps(t(), [rcl_app_info:t()]) -> t(). -available_apps(M, NewApps) -> - M#state_t{available_apps=NewApps}. - --spec get(t(), atom()) -> term(). -get(#state_t{config_values=Config}, Key) - when erlang:is_atom(Key) -> - ec_dictionary:get(Key, Config). - --spec get(t(), atom(), DefaultValue::term()) -> term(). -get(#state_t{config_values=Config}, Key, DefaultValue) - when erlang:is_atom(Key) -> - try - ec_dictionary:get(Key, Config) - catch - throw:not_found -> - DefaultValue - end. - --spec put(t(), atom(), term()) ->t(). -put(M=#state_t{config_values=Config}, Key, Value) - when erlang:is_atom(Key) -> - M#state_t{config_values=ec_dictionary:add(Key, Value, Config)}. - --spec caller(t()) -> caller(). -caller(#state_t{caller=Caller}) -> - Caller. - --spec caller(t(), caller()) -> t(). -caller(S, Caller) -> - S#state_t{caller=Caller}. - --spec upfrom(t()) -> string() | binary() | undefined. -upfrom(#state_t{upfrom=UpFrom}) -> - UpFrom. - --spec format(t()) -> iolist(). -format(Mod) -> - format(Mod, 0). - --spec format(t(), non_neg_integer()) -> iolist(). -format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, - caller=Caller, config_values=Values0, - goals=Goals, config_file=ConfigFile, - providers=Providers}, - Indent) -> - Values1 = ec_dictionary:to_list(Values0), - [rcl_util:indent(Indent), - <<"state(">>, erlang:atom_to_list(Caller), <<"):\n">>, - rcl_util:indent(Indent + 1), <<"log: ">>, rcl_log:format(LogState), <<",\n">>, - rcl_util:indent(Indent + 1), "config file: ", rcl_util:optional_to_string(ConfigFile), "\n", - rcl_util:indent(Indent + 1), "goals: \n", - [[rcl_util:indent(Indent + 2), rcl_depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], - rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", - rcl_util:indent(Indent + 1), "lib_dirs: \n", - [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs], - rcl_util:indent(Indent + 1), "providers: \n", - [[rcl_util:indent(Indent + 2), rcl_provider:format(Provider), ",\n"] || Provider <- Providers], - rcl_util:indent(Indent + 1), "provider config values: \n", - [[rcl_util:indent(Indent + 2), io_lib:format("~p", [Value]), ",\n"] || Value <- Values1]]. - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== - --spec create_logic_providers(t()) -> t(). -create_logic_providers(State0) -> - {ConfigProvider, {ok, State1}} = rcl_provider:new(rcl_prv_config, State0), - {DiscoveryProvider, {ok, State2}} = rcl_provider:new(rcl_prv_discover, State1), - {ReleaseProvider, {ok, State3}} = rcl_provider:new(rcl_prv_release, State2), - {OverlayProvider, {ok, State4}} = rcl_provider:new(rcl_prv_overlay, State3), - {AssemblerProvider, {ok, State5}} = rcl_provider:new(rcl_prv_assembler, State4), - State5#state_t{providers=[ConfigProvider, DiscoveryProvider, - ReleaseProvider, OverlayProvider, AssemblerProvider]}. - -to_binary(Dir) - when erlang:is_list(Dir) -> - erlang:list_to_binary(Dir); -to_binary(Dir) - when erlang:is_binary(Dir) -> - Dir. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - -new_test() -> - LogState = rcl_log:new(error), - RCLState = new([{log, LogState}], release), - ?assertMatch(LogState, log(RCLState)). - --endif. diff --git a/src/rcl_topo.erl b/src/rcl_topo.erl deleted file mode 100644 index 462b7c5..0000000 --- a/src/rcl_topo.erl +++ /dev/null @@ -1,217 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Joe Armstrong -%%% @author Eric Merritt -%%% @doc -%%% This is a pretty simple topological sort for erlang. It was -%%% originally written for ermake by Joe Armstrong back in '98. It -%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relcool. -%%% -%%% A partial order on the set S is a set of pairs {Xi,Xj} such that -%%% some relation between Xi and Xj is obeyed. -%%% -%%% A topological sort of a partial order is a sequence of elements -%%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial -%%% order i < j -%%% @end -%%%------------------------------------------------------------------- --module(rcl_topo). - --export([sort_apps/1, - format_error/1]). - --include_lib("relcool/include/relcool.hrl"). - -%%==================================================================== -%% Types -%%==================================================================== --type pair() :: {DependentApp::atom(), PrimaryApp::atom()}. --type name() :: AppName::atom(). --type element() :: name() | pair(). - -%%==================================================================== -%% API -%%==================================================================== - -%% @doc This only does a topo sort on the list of applications and -%% assumes that there is only *one* version of each app in the list of -%% applications. This implies that you have already done the -%% constraint solve before you pass the list of apps here to be -%% sorted. --spec sort_apps([rcl_app_info:t()]) -> - {ok, [rcl_app_info:t()]} | - relcool:error(). -sort_apps(Apps) -> - Pairs = apps_to_pairs(Apps), - case sort(Pairs) of - {ok, Names} -> - {ok, names_to_apps(Names, Apps)}; - E -> - E - end. -%% @doc nicely format the error from the sort. --spec format_error(Reason::term()) -> iolist(). -format_error({cycle, Pairs}) -> - ["Cycle detected in dependency graph, this must be resolved " - "before we can continue:\n", - case Pairs of - [{P1, P2}] -> - [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1)]; - [{P1, P2} | Rest] -> - [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1), - [["-> ", erlang:atom_to_list(PP2), " -> ", erlang:atom_to_list(PP1)] || {PP1, PP2} <- Rest]]; - [] -> - [] - end]. - -%%==================================================================== -%% Internal Functions -%%==================================================================== -%% @doc Do a topological sort on the list of pairs. --spec sort([pair()]) -> {ok, [atom()]} | relcool:error(). -sort(Pairs) -> - iterate(Pairs, [], all(Pairs)). - --spec names_to_apps([atom()], [rcl_app_info:t()]) -> [rcl_app_info:t()]. -names_to_apps(Names, Apps) -> - [find_app_by_name(Name, Apps) || Name <- Names]. - --spec find_app_by_name(atom(), [rcl_app_info:t()]) -> rcl_app_info:t(). -find_app_by_name(Name, Apps) -> - {ok, App1} = - ec_lists:find(fun(App) -> - rcl_app_info:name(App) =:= Name - end, Apps), - App1. - --spec apps_to_pairs([rcl_app_info:t()]) -> [pair()]. -apps_to_pairs(Apps) -> - lists:flatten([app_to_pairs(App) || App <- Apps]). - --spec app_to_pairs(rcl_app_info:t()) -> [pair()]. -app_to_pairs(App) -> - [{DepApp, rcl_app_info:name(App)} || - DepApp <- - rcl_app_info:active_deps(App) ++ - rcl_app_info:library_deps(App)]. - - -%% @doc Iterate over the system. @private --spec iterate([pair()], [name()], [name()]) -> - {ok, [name()]} | relcool:error(). -iterate([], L, All) -> - {ok, remove_duplicates(L ++ subtract(All, L))}; -iterate(Pairs, L, All) -> - case subtract(lhs(Pairs), rhs(Pairs)) of - [] -> - ?RCL_ERROR({cycle, Pairs}); - Lhs -> - iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) - end. - --spec all([pair()]) -> [atom()]. -all(L) -> - lhs(L) ++ rhs(L). - --spec lhs([pair()]) -> [atom()]. -lhs(L) -> - [X || {X, _} <- L]. - --spec rhs([pair()]) -> [atom()]. -rhs(L) -> - [Y || {_, Y} <- L]. - -%% @doc all the elements in L1 which are not in L2 -%% @private --spec subtract([element()], [element()]) -> [element()]. -subtract(L1, L2) -> - [X || X <- L1, not lists:member(X, L2)]. - -%% @doc remove dups from the list. @private --spec remove_duplicates([element()]) -> [element()]. -remove_duplicates([H|T]) -> - case lists:member(H, T) of - true -> - remove_duplicates(T); - false -> - [H|remove_duplicates(T)] - end; -remove_duplicates([]) -> - []. - -%% @doc -%% removes all pairs from L2 where the first element -%% of each pair is a member of L1 -%% -%% L2' L1 = [X] L2 = [{X,Y}]. -%% @private --spec remove_pairs([atom()], [pair()]) -> [pair()]. -remove_pairs(L1, L2) -> - [All || All={X, _Y} <- L2, not lists:member(X, L1)]. - -%%==================================================================== -%% Tests -%%==================================================================== --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - -topo_1_test() -> - Pairs = [{one,two},{two,four},{four,six}, - {two,ten},{four,eight}, - {six,three},{one,three}, - {three,five},{five,eight}, - {seven,five},{seven,nine}, - {nine,four},{nine,ten}], - ?assertMatch({ok, [one,seven,two,nine,four,six,three,five,eight,ten]}, - sort(Pairs)). -topo_2_test() -> - Pairs = [{app2, app1}, {zapp1, app1}, {stdlib, app1}, - {app3, app2}, {kernel, app1}, {kernel, app3}, - {app2, zapp1}, {app3, zapp1}, {zapp2, zapp1}], - ?assertMatch({ok, [stdlib, kernel, zapp2, - app3, app2, zapp1, app1]}, - sort(Pairs)). - -topo_pairs_cycle_test() -> - Pairs = [{app2, app1}, {app1, app2}, {stdlib, app1}], - ?assertMatch({error, {_, {cycle, [{app2, app1}, {app1, app2}]}}}, - sort(Pairs)). - -topo_apps_cycle_test() -> - {ok, App1} = rcl_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]), - {ok, App2} = rcl_app_info:new(app2, "0.1", "/no-dir", [app1], []), - Apps = [App1, App2], - ?assertMatch({error, {_, {cycle, [{app2,app1},{app1,app2}]}}}, - sort_apps(Apps)). - -topo_apps_good_test() -> - Apps = [App || - {ok, App} <- - [rcl_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]), - rcl_app_info:new(app2, "0.1", "/no-dir", [app3], []), - rcl_app_info:new(app3, "0.1", "/no-dir", [kernel], []), - rcl_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []), - rcl_app_info:new(stdlib, "0.1", "/no-dir", [], []), - rcl_app_info:new(kernel, "0.1", "/no-dir", [], []), - rcl_app_info:new(zapp2, "0.1", "/no-dir", [], [])]], - {ok, Sorted} = sort_apps(Apps), - ?assertMatch([stdlib, kernel, zapp2, - app3, app2, zapp1, app1], - [rcl_app_info:name(App) || App <- Sorted]). - --endif. diff --git a/src/rcl_util.erl b/src/rcl_util.erl deleted file mode 100644 index 4afb26d..0000000 --- a/src/rcl_util.erl +++ /dev/null @@ -1,93 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% -%%% @doc Trivial utility file to help handle common tasks --module(rcl_util). - --export([mkdir_p/1, - to_binary/1, - to_string/1, - is_error/1, - error_reason/1, - indent/1, - optional_to_string/1]). - --define(ONE_LEVEL_INDENT, " "). -%%============================================================================ -%% types -%%============================================================================ - - -%%============================================================================ -%% API -%%============================================================================ -%% @doc Makes a directory including parent dirs if they are missing. --spec mkdir_p(string()) -> ok | {error, Reason::file:posix()}. -mkdir_p(Path) -> - %% We are exploiting a feature of ensuredir that that creates all - %% directories up to the last element in the filename, then ignores - %% that last element. This way we ensure that the dir is created - %% and not have any worries about path names - DirName = filename:join([filename:absname(Path), "tmp"]), - filelib:ensure_dir(DirName). - -%% @doc ident to the level specified --spec indent(non_neg_integer()) -> iolist(). -indent(Amount) when erlang:is_integer(Amount) -> - [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. - --spec to_binary(iolist() | binary()) -> binary(). -to_binary(String) when erlang:is_list(String) -> - erlang:iolist_to_binary(String); -to_binary(Bin) when erlang:is_binary(Bin) -> - Bin. - -to_string(Binary) when erlang:is_binary(Binary) -> - erlang:binary_to_list(Binary); -to_string(Atom) when erlang:is_atom(Atom) -> - erlang:atom_to_list(Atom); -to_string(Else) when erlang:is_list(Else) -> - Else. - -%% @doc get the reason for a particular relcool error --spec error_reason(relcool:error()) -> any(). -error_reason({error, {_, Reason}}) -> - Reason. -%% @doc check to see if the value is a relcool error --spec is_error(relcool:error() | any()) -> boolean(). -is_error({error, _}) -> - true; -is_error(_) -> - false. - -%% @doc convert optional argument to empty string if undefined -optional_to_string(undefined) -> - ""; -optional_to_string(Value) when is_list(Value) -> - Value. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - --endif. diff --git a/src/relcool.app.src b/src/relcool.app.src deleted file mode 100644 index 0c1c45a..0000000 --- a/src/relcool.app.src +++ /dev/null @@ -1,26 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%% -%% @author Eric Merritt -%% @copyright Erlware, Inc. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% - -{application, relcool, - [{description, "Release assembler for Erlang/OTP Releases"}, - {vsn, "0.0.5"}, - {modules, []}, - {registered, []}, - {applications, [kernel, stdlib, getopt, erlware_commons]}]}. diff --git a/src/relcool.erl b/src/relcool.erl deleted file mode 100644 index c404e6f..0000000 --- a/src/relcool.erl +++ /dev/null @@ -1,278 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% @doc --module(relcool). - --export([main/1, - do/2, - do/7, - do/8, - do/9, - format_error/1, - opt_spec_list/0]). - --export_type([error/0]). - --include_lib("relcool/include/relcool.hrl"). - -%%============================================================================ -%% types -%%============================================================================ - --type error() :: {error, {Module::module(), Reason::term()}}. --type goal() :: string() | binary() | rcl_depsolver:constraint(). - -%%============================================================================ -%% API -%%============================================================================ --spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. -main(Args) -> - OptSpecList = opt_spec_list(), - Result = case getopt:parse(OptSpecList, Args) of - {ok, {Options, NonOptions}} -> - do([{caller, command_line} | Options], NonOptions); - {error, Detail} -> - ?RCL_ERROR({opt_parse, Detail}) - end, - case Result of - {error, _} -> - report_error(rcl_state:caller(rcl_state:new([], undefined), - command_line), - Result); - _ -> - Result - end. - -%% @doc provides an API to run the Relcool process from erlang applications -%% -%% @param RelName - The release name to build (maybe `undefined`) -%% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal -%% format -%% @param LibDirs - The library dirs that should be used for the system -%% @param OutputDir - The directory where the release should be built to -%% @param Configs - The list of config files for the system --spec do(atom(), string(), [goal()], [file:name()], rcl_log:log_level(), - [file:name()], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. -do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Config) -> - {ok, Cwd} = file:get_cwd(), - do(Cwd, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Config). - -%% @doc provides an API to run the Relcool process from erlang applications -%% -%% @param RootDir - The root directory for the project -%% @param RelName - The release name to build (maybe `undefined`) -%% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal -%% format -%% @param LibDirs - The library dirs that should be used for the system -%% @param OutputDir - The directory where the release should be built to -%% @param Configs - The list of config files for the system --spec do(file:name(), atom(), string(), [goal()], [file:name()], - rcl_log:log_level(), [file:name()], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. -do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> - do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Configs). - -%% @doc provides an API to run the Relcool process from erlang applications -%% -%% @param RootDir - The root directory for the system -%% @param RelName - The release name to build (maybe `undefined`) -%% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal -%% format -%% @param LibDirs - The library dirs that should be used for the system -%% @param OutputDir - The directory where the release should be built to -%% @param Overrides - A list of overrides for the system -%% @param Configs - The list of config files for the system --spec do(file:name(), atom(), string(), [goal()], [file:name()], - rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. -do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> - do([{relname, RelName}, - {relvsn, RelVsn}, - {goals, Goals}, - {overrides, Overrides}, - {output_dir, OutputDir}, - {lib_dirs, LibDirs}, - {root_dir, RootDir}, - {log_level, LogLevel}, - {config, Config}], - ["release"]). - -%% @doc provides an API to run the Relcool process from erlang applications -%% -%% @param Opts - A proplist of options. There are good defaults for each of -%% these entries, so any or all may be omitted. Individual options may be: -%% -%%
-%%
{relname, RelName}
-%%
The release name to build
-%%
{relvsn, RelVsn}
-%%
The release version to build
-%%
{goals, Goals}
-%%
The release goals for the system in depsolver or Relcool goal -%% format (@see goals())
-%%
{lib_dirs, LibDirs}
-%%
A list of library dirs that should be used for the system
-%%
{lib_dir, LibDir}
-%%
A single lib dir that should be used for the system, may appear any -%% number of times and may be used in conjunction with lib_dirs
-%%
{output_dir, OutputDir}
-%%
The directory where the release should be built to
-%%
{root_dir, RootDir}
-%%
The base directory for this run of relcool.
-%%
{config, Config}
-%%
The path to a relcool config file
-%%
{log_level, LogLevel}
-%%
Defines the verbosity of output. Maybe a number between 0 and 2, with -%% with higher values being more verbose
-%%
{overrides, Overrides}
-%%
A list of app overrides for the system in the form of [{AppName:atom(), -%% Dir:string() | binary()} | string() | binary()] where the string or binary -%% is in the form "AppName:AppDir"
-%%
{override, Override}
-%%
A single of app override for the system in the same form as entries for -%% Overrides
-%%
--spec do(proplists:proplist(), [string()]) -> - ok | error() | {ok, rcl_state:t()}. -do(Opts, NonOpts) -> - case rcl_cmd_args:args2state(Opts, NonOpts) of - {ok, State} -> - run_relcool_process(State); - Error={error, _} -> - Error - end. - --spec opt_spec_list() -> [getopt:option_spec()]. -opt_spec_list() -> - [{relname, $n, "relname", string, - "Specify the name for the release that will be generated"}, - {relvsn, $v, "relvsn", string, "Specify the version for the release"}, - {goal, $g, "goal", string, - "Specify a target constraint on the system. These are usually the OTP"}, - {upfrom, $u, "upfrom", string, - "Only valid with relup target, specify the release to upgrade from"}, - {output_dir, $o, "output-dir", string, - "The output directory for the release. This is `./` by default."}, - {lib_dir, $l, "lib-dir", string, - "Additional dirs that should be searched for OTP Apps"}, - {disable_default_libs, undefined, "disable-default-libs", - {boolean, false}, - "Disable the default system added lib dirs (means you must add them all manually"}, - {log_level, $V, "verbose", {integer, 1}, - "Verbosity level, maybe between 0 and 2"}, - {override_app, $a, "override_app", string, - "Provide an app name and a directory to override in the form :"}, - {config, $c, "config", {string, ""}, "The path to a config file"}, - {root_dir, $r, "root", string, "The project root directory"}]. - --spec format_error(Reason::term()) -> iolist(). -format_error({invalid_return_value, Provider, Value}) -> - [rcl_provider:format(Provider), " returned an invalid value ", - io_lib:format("~p", [Value])]; -format_error({opt_parse, {invalid_option, Opt}}) -> - io_lib:format("invalid option ~s~n", [Opt]); -format_error({opt_parse, Arg}) -> - io_lib:format("~p~n", [Arg]); -format_error({error, {Module, Reason}}) -> - io_lib:format("~s~n", [Module:format_error(Reason)]). - -%%============================================================================ -%% internal api -%%============================================================================ -run_relcool_process(State) -> - rcl_log:info(rcl_state:log(State), "Starting relcool build process ..."), - rcl_log:debug(rcl_state:log(State), - fun() -> - rcl_state:format(State) - end), - run_providers(State). - -%% @doc for now the 'config' provider is special in that it generates the -%% providers used by the rest of the system. We expect the config provider to be -%% the first provider in the system. Once the config provider is run, we get the -%% providers again and run the rest of them (because they could have been -%% updated by the config process). -run_providers(State0) -> - [ConfigProvider | _] = rcl_state:providers(State0), - case run_provider(ConfigProvider, {ok, State0}) of - Err = {error, _} -> - Err; - {ok, State1} -> - RootDir = rcl_state:root_dir(State1), - ok = file:set_cwd(RootDir), - Providers = rcl_state:providers(State1), - Result = run_providers(ConfigProvider, Providers, State1), - handle_output(State1, rcl_state:caller(State1), Result) - end. - -handle_output(State, command_line, E={error, _}) -> - report_error(State, E), - init:stop(127); -handle_output(_State, command_line, _) -> - init:stop(0); -handle_output(_State, api, Result) -> - Result. - -run_providers(ConfigProvider, Providers, State0) -> - case Providers of - [ConfigProvider | Rest] -> - %% IF the config provider is still the first provider do not run it - %% again just run the rest. - lists:foldl(fun run_provider/2, {ok, State0}, Rest); - _ -> - lists:foldl(fun run_provider/2, {ok, State0}, Providers) - end. - --spec run_provider(rcl_provider:t(), {ok, rcl_state:t()} | error()) -> - {ok, rcl_state:t()} | error(). -run_provider(_Provider, Error = {error, _}) -> - Error; -run_provider(Provider, {ok, State0}) -> - rcl_log:debug(rcl_state:log(State0), "Running provider ~p~n", - [rcl_provider:impl(Provider)]), - case rcl_provider:do(Provider, State0) of - {ok, State1} -> - rcl_log:debug(rcl_state:log(State0), "Provider successfully run: ~p~n", - [rcl_provider:impl(Provider)]), - {ok, State1}; - E={error, _} -> - rcl_log:debug(rcl_state:log(State0), "Provider (~p) failed with: ~p~n", - [rcl_provider:impl(Provider), E]), - E - end. - --spec usage() -> ok. -usage() -> - getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]"). - --spec report_error(rcl_state:t(), error()) -> none() | error(). -report_error(State, Error) -> - io:format(format_error(Error)), - usage(), - case rcl_state:caller(State) of - command_line -> - erlang:halt(127); - api -> - Error - end. diff --git a/src/relx.app.src b/src/relx.app.src new file mode 100644 index 0000000..0c1c45a --- /dev/null +++ b/src/relx.app.src @@ -0,0 +1,26 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%% +%% @author Eric Merritt +%% @copyright Erlware, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% + +{application, relcool, + [{description, "Release assembler for Erlang/OTP Releases"}, + {vsn, "0.0.5"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib, getopt, erlware_commons]}]}. diff --git a/src/relx.erl b/src/relx.erl new file mode 100644 index 0000000..c404e6f --- /dev/null +++ b/src/relx.erl @@ -0,0 +1,278 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% @doc +-module(relcool). + +-export([main/1, + do/2, + do/7, + do/8, + do/9, + format_error/1, + opt_spec_list/0]). + +-export_type([error/0]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% types +%%============================================================================ + +-type error() :: {error, {Module::module(), Reason::term()}}. +-type goal() :: string() | binary() | rcl_depsolver:constraint(). + +%%============================================================================ +%% API +%%============================================================================ +-spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. +main(Args) -> + OptSpecList = opt_spec_list(), + Result = case getopt:parse(OptSpecList, Args) of + {ok, {Options, NonOptions}} -> + do([{caller, command_line} | Options], NonOptions); + {error, Detail} -> + ?RCL_ERROR({opt_parse, Detail}) + end, + case Result of + {error, _} -> + report_error(rcl_state:caller(rcl_state:new([], undefined), + command_line), + Result); + _ -> + Result + end. + +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param RelName - The release name to build (maybe `undefined`) +%% @param RelVsn - The release version to build (maybe `undefined`) +%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% format +%% @param LibDirs - The library dirs that should be used for the system +%% @param OutputDir - The directory where the release should be built to +%% @param Configs - The list of config files for the system +-spec do(atom(), string(), [goal()], [file:name()], rcl_log:log_level(), + [file:name()], file:name() | undefined) -> + ok | error() | {ok, rcl_state:t()}. +do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Config) -> + {ok, Cwd} = file:get_cwd(), + do(Cwd, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Config). + +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param RootDir - The root directory for the project +%% @param RelName - The release name to build (maybe `undefined`) +%% @param RelVsn - The release version to build (maybe `undefined`) +%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% format +%% @param LibDirs - The library dirs that should be used for the system +%% @param OutputDir - The directory where the release should be built to +%% @param Configs - The list of config files for the system +-spec do(file:name(), atom(), string(), [goal()], [file:name()], + rcl_log:log_level(), [file:name()], file:name() | undefined) -> + ok | error() | {ok, rcl_state:t()}. +do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> + do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Configs). + +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param RootDir - The root directory for the system +%% @param RelName - The release name to build (maybe `undefined`) +%% @param RelVsn - The release version to build (maybe `undefined`) +%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% format +%% @param LibDirs - The library dirs that should be used for the system +%% @param OutputDir - The directory where the release should be built to +%% @param Overrides - A list of overrides for the system +%% @param Configs - The list of config files for the system +-spec do(file:name(), atom(), string(), [goal()], [file:name()], + rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> + ok | error() | {ok, rcl_state:t()}. +do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> + do([{relname, RelName}, + {relvsn, RelVsn}, + {goals, Goals}, + {overrides, Overrides}, + {output_dir, OutputDir}, + {lib_dirs, LibDirs}, + {root_dir, RootDir}, + {log_level, LogLevel}, + {config, Config}], + ["release"]). + +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param Opts - A proplist of options. There are good defaults for each of +%% these entries, so any or all may be omitted. Individual options may be: +%% +%%
+%%
{relname, RelName}
+%%
The release name to build
+%%
{relvsn, RelVsn}
+%%
The release version to build
+%%
{goals, Goals}
+%%
The release goals for the system in depsolver or Relcool goal +%% format (@see goals())
+%%
{lib_dirs, LibDirs}
+%%
A list of library dirs that should be used for the system
+%%
{lib_dir, LibDir}
+%%
A single lib dir that should be used for the system, may appear any +%% number of times and may be used in conjunction with lib_dirs
+%%
{output_dir, OutputDir}
+%%
The directory where the release should be built to
+%%
{root_dir, RootDir}
+%%
The base directory for this run of relcool.
+%%
{config, Config}
+%%
The path to a relcool config file
+%%
{log_level, LogLevel}
+%%
Defines the verbosity of output. Maybe a number between 0 and 2, with +%% with higher values being more verbose
+%%
{overrides, Overrides}
+%%
A list of app overrides for the system in the form of [{AppName:atom(), +%% Dir:string() | binary()} | string() | binary()] where the string or binary +%% is in the form "AppName:AppDir"
+%%
{override, Override}
+%%
A single of app override for the system in the same form as entries for +%% Overrides
+%%
+-spec do(proplists:proplist(), [string()]) -> + ok | error() | {ok, rcl_state:t()}. +do(Opts, NonOpts) -> + case rcl_cmd_args:args2state(Opts, NonOpts) of + {ok, State} -> + run_relcool_process(State); + Error={error, _} -> + Error + end. + +-spec opt_spec_list() -> [getopt:option_spec()]. +opt_spec_list() -> + [{relname, $n, "relname", string, + "Specify the name for the release that will be generated"}, + {relvsn, $v, "relvsn", string, "Specify the version for the release"}, + {goal, $g, "goal", string, + "Specify a target constraint on the system. These are usually the OTP"}, + {upfrom, $u, "upfrom", string, + "Only valid with relup target, specify the release to upgrade from"}, + {output_dir, $o, "output-dir", string, + "The output directory for the release. This is `./` by default."}, + {lib_dir, $l, "lib-dir", string, + "Additional dirs that should be searched for OTP Apps"}, + {disable_default_libs, undefined, "disable-default-libs", + {boolean, false}, + "Disable the default system added lib dirs (means you must add them all manually"}, + {log_level, $V, "verbose", {integer, 1}, + "Verbosity level, maybe between 0 and 2"}, + {override_app, $a, "override_app", string, + "Provide an app name and a directory to override in the form :"}, + {config, $c, "config", {string, ""}, "The path to a config file"}, + {root_dir, $r, "root", string, "The project root directory"}]. + +-spec format_error(Reason::term()) -> iolist(). +format_error({invalid_return_value, Provider, Value}) -> + [rcl_provider:format(Provider), " returned an invalid value ", + io_lib:format("~p", [Value])]; +format_error({opt_parse, {invalid_option, Opt}}) -> + io_lib:format("invalid option ~s~n", [Opt]); +format_error({opt_parse, Arg}) -> + io_lib:format("~p~n", [Arg]); +format_error({error, {Module, Reason}}) -> + io_lib:format("~s~n", [Module:format_error(Reason)]). + +%%============================================================================ +%% internal api +%%============================================================================ +run_relcool_process(State) -> + rcl_log:info(rcl_state:log(State), "Starting relcool build process ..."), + rcl_log:debug(rcl_state:log(State), + fun() -> + rcl_state:format(State) + end), + run_providers(State). + +%% @doc for now the 'config' provider is special in that it generates the +%% providers used by the rest of the system. We expect the config provider to be +%% the first provider in the system. Once the config provider is run, we get the +%% providers again and run the rest of them (because they could have been +%% updated by the config process). +run_providers(State0) -> + [ConfigProvider | _] = rcl_state:providers(State0), + case run_provider(ConfigProvider, {ok, State0}) of + Err = {error, _} -> + Err; + {ok, State1} -> + RootDir = rcl_state:root_dir(State1), + ok = file:set_cwd(RootDir), + Providers = rcl_state:providers(State1), + Result = run_providers(ConfigProvider, Providers, State1), + handle_output(State1, rcl_state:caller(State1), Result) + end. + +handle_output(State, command_line, E={error, _}) -> + report_error(State, E), + init:stop(127); +handle_output(_State, command_line, _) -> + init:stop(0); +handle_output(_State, api, Result) -> + Result. + +run_providers(ConfigProvider, Providers, State0) -> + case Providers of + [ConfigProvider | Rest] -> + %% IF the config provider is still the first provider do not run it + %% again just run the rest. + lists:foldl(fun run_provider/2, {ok, State0}, Rest); + _ -> + lists:foldl(fun run_provider/2, {ok, State0}, Providers) + end. + +-spec run_provider(rcl_provider:t(), {ok, rcl_state:t()} | error()) -> + {ok, rcl_state:t()} | error(). +run_provider(_Provider, Error = {error, _}) -> + Error; +run_provider(Provider, {ok, State0}) -> + rcl_log:debug(rcl_state:log(State0), "Running provider ~p~n", + [rcl_provider:impl(Provider)]), + case rcl_provider:do(Provider, State0) of + {ok, State1} -> + rcl_log:debug(rcl_state:log(State0), "Provider successfully run: ~p~n", + [rcl_provider:impl(Provider)]), + {ok, State1}; + E={error, _} -> + rcl_log:debug(rcl_state:log(State0), "Provider (~p) failed with: ~p~n", + [rcl_provider:impl(Provider), E]), + E + end. + +-spec usage() -> ok. +usage() -> + getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]"). + +-spec report_error(rcl_state:t(), error()) -> none() | error(). +report_error(State, Error) -> + io:format(format_error(Error)), + usage(), + case rcl_state:caller(State) of + command_line -> + erlang:halt(127); + api -> + Error + end. diff --git a/src/rlx_app_discovery.erl b/src/rlx_app_discovery.erl new file mode 100644 index 0000000..7b04b19 --- /dev/null +++ b/src/rlx_app_discovery.erl @@ -0,0 +1,207 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provider uses the lib_dir setting of the state. It searches the +%%% Lib Dirs looking for all OTP Applications that are available. When it finds +%%% those OTP Applications it loads the information about them and adds them to +%%% the state of available apps. This implements the rcl_provider behaviour. +-module(rcl_app_discovery). + +-export([do/2, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t(), [filename:name()]) -> {ok, [rcl_app_info:t()]} | relcool:error(). +do(State, LibDirs) -> + rcl_log:info(rcl_state:log(State), + fun() -> + ["Resolving OTP Applications from directories:\n", + [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] + end), + resolve_app_metadata(State, LibDirs). + +-spec format_error([ErrorDetail::term()]) -> iolist(). +format_error(ErrorDetails) + when erlang:is_list(ErrorDetails) -> + [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +resolve_app_metadata(State, LibDirs) -> + AppMeta0 = lists:flatten(rcl_dscv_util:do(fun discover_dir/2, LibDirs)), + case [case Err of + {error, Ret} -> + Ret + end + || Err <- AppMeta0, + case Err of + {error, _} -> + true; + _ -> + false + end] of + [] -> + SkipApps = rcl_state:skip_apps(State), + AppMeta1 = [App || {ok, App} <- setup_overrides(State, AppMeta0), + not lists:keymember(rcl_app_info:name(App), 1, SkipApps)], + rcl_log:debug(rcl_state:log(State), + fun() -> + ["Resolved the following OTP Applications from the system: \n", + [[rcl_app_info:format(1, App), "\n"] || App <- AppMeta1]] + end), + {ok, AppMeta1}; + Errors -> + ?RCL_ERROR(Errors) + end. + +app_name({error, _}) -> + undefined; +app_name({ok, AppMeta}) -> + rcl_app_info:name(AppMeta). + +setup_overrides(State, AppMetas0) -> + Overrides = rcl_state:overrides(State), + AppMetas1 = [AppMeta || AppMeta <- AppMetas0, + not lists:keymember(app_name(AppMeta), 1, Overrides)], + [case is_valid_otp_app(filename:join([FileName, <<"ebin">>, + erlang:atom_to_list(AppName) ++ ".app"])) of + {noresult, false} -> + {error, {invalid_override, AppName, FileName}}; + Error = {error, _} -> + Error; + {ok, App} -> + {ok, rcl_app_info:link(App, true)} + end || {AppName, FileName} <- Overrides] ++ AppMetas1. + + +-spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({error, {invalid_override, AppName, FileName}}) -> + io_lib:format("Override {~p, ~p} is not a valid OTP App. Perhaps you forgot to build it?", + [AppName, FileName]); +format_detail({accessing, File, eaccess}) -> + io_lib:format("permission denied accessing file ~s", [File]); +format_detail({accessing, File, Type}) -> + io_lib:format("error (~p) accessing file ~s", [Type, File]); +format_detail({no_beam_files, EbinDir}) -> + io_lib:format("no beam files found in directory ~s", [EbinDir]); +format_detail({not_a_directory, EbinDir}) -> + io_lib:format("~s is not a directory when it should be a directory", [EbinDir]); +format_detail({unable_to_load_app, AppDir, _}) -> + io_lib:format("Unable to load the application metadata from ~s", [AppDir]); +format_detail({invalid_app_file, File}) -> + io_lib:format("Application metadata file exists but is malformed: ~s", + [File]); +format_detail({unversioned_app, AppDir, _AppName}) -> + io_lib:format("Application metadata exists but version is not available: ~s", + [AppDir]); +format_detail({app_info_error, {Module, Detail}}) -> + Module:format_error(Detail). + +-spec discover_dir([file:name()], directory | file) -> + {ok, rcl_app_info:t()} | {error, Reason::term()}. +discover_dir(_File, directory) -> + {noresult, true}; +discover_dir(File, file) -> + is_valid_otp_app(File). + +-spec is_valid_otp_app(file:name()) -> {ok, rcl_app_info:t()} | {error, Reason::term()} | + {noresult, false}. + +is_valid_otp_app(File) -> + %% Is this an ebin dir? + EbinDir = filename:dirname(File), + case filename:basename(EbinDir) of + <<"ebin">> -> + case filename:extension(File) of + <<".app">> -> + has_at_least_one_beam(EbinDir, File); + _ -> + {noresult, false} + end; + _ -> + {noresult, false} + end. + +-spec has_at_least_one_beam(file:name(), file:filename()) -> + {ok, rcl_app_info:t()} | {error, Reason::term()}. +has_at_least_one_beam(EbinDir, File) -> + case file:list_dir(EbinDir) of + {ok, List} -> + case lists:any(fun(NFile) -> lists:suffix(".beam", NFile) end, List) of + true -> + gather_application_info(EbinDir, File); + false -> + {error, {no_beam_files, EbinDir}} + end; + _ -> + {error, {not_a_directory, EbinDir}} + end. + +-spec gather_application_info(file:name(), file:filename()) -> + {ok, rcl_app_info:t()} | {error, Reason::term()}. +gather_application_info(EbinDir, File) -> + AppDir = filename:dirname(EbinDir), + case file:consult(File) of + {ok, [{application, AppName, AppDetail}]} -> + get_vsn(AppDir, AppName, AppDetail); + {error, Reason} -> + {error, {unable_to_load_app, AppDir, Reason}}; + _ -> + {error, {invalid_app_file, File}} + end. + +-spec get_vsn(file:name(), atom(), proplists:proplist()) -> + {ok, rcl_app_info:t()} | {error, Reason::term()}. +get_vsn(AppDir, AppName, AppDetail) -> + case proplists:get_value(vsn, AppDetail) of + undefined -> + {error, {unversioned_app, AppDir, AppName}}; + AppVsn -> + case get_deps(AppDir, AppName, AppVsn, AppDetail) of + {ok, App} -> + {ok, App}; + {error, Detail} -> + {error, {app_info_error, Detail}} + end + end. + +-spec get_deps(file:name(), atom(), string(), proplists:proplist()) -> + {ok, rcl_app_info:t()} | {error, Reason::term()}. +get_deps(AppDir, AppName, AppVsn, AppDetail) -> + ActiveApps = proplists:get_value(applications, AppDetail, []), + LibraryApps = proplists:get_value(included_applications, AppDetail, []), + rcl_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps). + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +-endif. diff --git a/src/rlx_app_info.erl b/src/rlx_app_info.erl new file mode 100644 index 0000000..dadc579 --- /dev/null +++ b/src/rlx_app_info.erl @@ -0,0 +1,193 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This module represents useful, relevant information about an +%%% application. The relevant information is. +%%% +%%%
    +%%%
  • Name - The application name as an atom
  • +%%%
  • Vsn - The application version as a list
  • +%%%
  • The root directory of the application. The directory that contains the +%%% ebin/src/priv etc
  • +%%%
  • Active Deps - The Active or 'application' dependencies of the OTP +%%% App. That is the things in the 'applications' property of the application +%%% metadata
  • +%%%
  • Library Deps - The Inactive or Library dependencies of the ATP +%%% app. That is the things in the 'included_applications property of the +%%% application metadata. +%%%
+%%% +-module(rcl_app_info). + +-export([new/0, + new/5, + name/1, + name/2, + vsn/1, + vsn/2, + vsn_as_string/1, + dir/1, + dir/2, + active_deps/1, + active_deps/2, + library_deps/1, + library_deps/2, + link/1, + link/2, + format_error/1, + format/2, + format/1]). + +-export_type([t/0]). + +-include_lib("relcool/include/relcool.hrl"). + +-record(app_info_t, {name :: atom(), + vsn :: ec_semver:semver(), + dir :: file:name(), + link=false :: boolean(), + active_deps=[]:: [atom()], + library_deps=[] :: [atom()]}). + +%%============================================================================ +%% types +%%============================================================================ +-opaque t() :: record(app_info_t). + +%%============================================================================ +%% API +%% ============================================================================ +%% @doc Build a new, empty, app info value. This is not of a lot of use and you +%% probably wont be doing this much. +-spec new() -> {ok, t()}. +new() -> + {ok, #app_info_t{}}. + +%% @doc build a complete version of the app info with all fields set. +-spec new(atom(), string(), file:name(), [atom()], [atom()]) -> + {ok, t()} | relcool:error(). +new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) -> + new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, false). + +%% @doc build a complete version of the app info with all fields set. +-spec new(atom(), string(), file:name(), [atom()], [atom()], boolean()) -> + {ok, t()} | relcool:error(). +new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, Link) + when erlang:is_atom(AppName), + erlang:is_list(ActiveDeps), + erlang:is_list(LibraryDeps) -> + case parse_version(Vsn) of + {fail, _} -> + ?RCL_ERROR({vsn_parse, AppName}); + ParsedVsn -> + {ok, #app_info_t{name=AppName, vsn=ParsedVsn, dir=Dir, + active_deps=ActiveDeps, + library_deps=LibraryDeps, + link=Link}} + end. + +-spec name(t()) -> atom(). +name(#app_info_t{name=Name}) -> + Name. + +-spec name(t(), atom()) -> t(). +name(AppInfo=#app_info_t{}, AppName) + when erlang:is_atom(AppName) -> + AppInfo#app_info_t{name=AppName}. + +-spec vsn(t()) -> ec_semver:semver(). +vsn(#app_info_t{vsn=Vsn}) -> + Vsn. + +-spec vsn_as_string(t()) -> string(). +vsn_as_string(#app_info_t{vsn=Vsn}) -> + erlang:binary_to_list(erlang:iolist_to_binary(ec_semver:format(Vsn))). + +-spec vsn(t(), string()) -> {ok, t()} | relcool:error(). +vsn(AppInfo=#app_info_t{name=AppName}, AppVsn) + when erlang:is_list(AppVsn) -> + case parse_version(AppVsn) of + {fail, _} -> + ?RCL_ERROR({vsn_parse, AppName}); + ParsedVsn -> + {ok, AppInfo#app_info_t{vsn=ParsedVsn}} + end. + +-spec dir(t()) -> file:name(). +dir(#app_info_t{dir=Dir}) -> + Dir. +-spec dir(t(), file:name()) -> t(). +dir(AppInfo=#app_info_t{}, Dir) -> + AppInfo#app_info_t{dir=Dir}. + +-spec active_deps(t()) -> [atom()]. +active_deps(#app_info_t{active_deps=Deps}) -> + Deps. +-spec active_deps(t(), [atom()]) -> t(). +active_deps(AppInfo=#app_info_t{}, ActiveDeps) + when erlang:is_list(ActiveDeps) -> + AppInfo#app_info_t{active_deps=ActiveDeps}. + +-spec library_deps(t()) -> [atom()]. +library_deps(#app_info_t{library_deps=Deps}) -> + Deps. + +-spec library_deps(t(), [atom()]) -> t(). +library_deps(AppInfo=#app_info_t{}, LibraryDeps) + when erlang:is_list(LibraryDeps) -> + AppInfo#app_info_t{library_deps=LibraryDeps}. + +-spec link(t()) -> boolean(). +link(#app_info_t{link=Link}) -> + Link. + +-spec link(t(), boolean()) -> t(). +link(AppInfo, NewLink) -> + AppInfo#app_info_t{link=NewLink}. + +-spec format_error(Reason::term()) -> iolist(). +format_error({vsn_parse, AppName}) -> + io_lib:format("Error parsing version for ~p", + [AppName]). + +-spec format(t()) -> iolist(). +format(AppInfo) -> + format(0, AppInfo). + +-spec format(non_neg_integer(), t()) -> iolist(). +format(Indent, #app_info_t{name=Name, vsn=Vsn, dir=Dir, + active_deps=Deps, library_deps=LibDeps, + link=Link}) -> + [rcl_util:indent(Indent), erlang:atom_to_list(Name), "-", ec_semver:format(Vsn), + ": ", Dir, "\n", + rcl_util:indent(Indent + 1), "Symlink: ", erlang:atom_to_list(Link), "\n", + rcl_util:indent(Indent + 1), "Active Dependencies:\n", + [[rcl_util:indent(Indent + 2), erlang:atom_to_list(Dep), ",\n"] || Dep <- Deps], + rcl_util:indent(Indent + 1), "Library Dependencies:\n", + [[rcl_util:indent(Indent + 2), erlang:atom_to_list(LibDep), ",\n"] || LibDep <- LibDeps]]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +parse_version(Vsn) + when erlang:is_list(Vsn) -> + ec_semver:parse(Vsn); +parse_version(Vsn = {_, {_, _}}) -> + Vsn. diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl new file mode 100644 index 0000000..fee4449 --- /dev/null +++ b/src/rlx_cmd_args.erl @@ -0,0 +1,281 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Trivial utility file to help handle common tasks +-module(rcl_cmd_args). + +-export([args2state/2, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec args2state([getopt:option()], [string()]) -> + {ok, {rcl_state:t(), [string()]}} | + relcool:error(). +args2state(Opts, Target) + when erlang:length(Target) == 0; erlang:length(Target) == 1 -> + RelName = proplists:get_value(relname, Opts, undefined), + RelVsn = proplists:get_value(relvsn, Opts, undefined), + case convert_target(Target) of + {ok, AtomizedTarget} -> + case create_log(Opts, [{relname, RelName}, + {relvsn, RelVsn}]) of + Error = {error, _} -> + Error; + {ok, CommandLineConfig} -> + handle_config(Opts, AtomizedTarget, CommandLineConfig) + end; + Error -> + Error + end; +args2state(_Opts, Targets) -> + ?RCL_ERROR({invalid_targets, Targets}). + +-spec format_error(Reason::term()) -> iolist(). +format_error({invalid_targets, Targets}) -> + io_lib:format("One config must be specified! not ~p~n", [Targets]); +format_error({invalid_option_arg, Arg}) -> + case Arg of + {goals, Goal} -> + io_lib:format("Invalid Goal argument -g ~p~n", [Goal]); + {relname, RelName} -> + io_lib:format("Invalid Release Name argument -n ~p~n", [RelName]); + {relvsn, RelVsn} -> + io_lib:format("Invalid Release Version argument -n ~p~n", [RelVsn]); + {output_dir, Outdir} -> + io_lib:format("Invalid Output Directory argument -n ~p~n", [Outdir]); + {lib_dir, LibDir} -> + io_lib:format("Invalid Library Directory argument -n ~p~n", [LibDir]); + {log_level, LogLevel} -> + io_lib:format("Invalid Library Directory argument -n ~p~n", [LogLevel]) + end; +format_error({invalid_config_file, Config}) -> + io_lib:format("Invalid configuration file specified: ~s", [Config]); +format_error({invalid_caller, Caller}) -> + io_lib:format("Invalid caller specified: ~s", [Caller]); +format_error({failed_to_parse, Spec}) -> + io_lib:format("Unable to parse spec ~s", [Spec]); +format_error({failed_to_parse_override, QA}) -> + io_lib:format("Failed to parse app override ~s", [QA]); +format_error({not_directory, Dir}) -> + io_lib:format("Library directory does not exist: ~s", [Dir]); +format_error({invalid_log_level, LogLevel}) -> + io_lib:format("Invalid log level specified -V ~p, log level must be in the" + " range 0..2", [LogLevel]); +format_error({invalid_target, Target}) -> + io_lib:format("Invalid action specified: ~s", [Target]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec handle_config([getopt:option()], atom(), proplists:proplist()) -> + {ok, {rcl_state:t(), [string()]}} | + relcool:error(). +handle_config(Opts, Target, CommandLineConfig) -> + case validate_config(proplists:get_value(config, Opts, [])) of + Error = {error, _} -> + Error; + {ok, Config} -> + {ok, rcl_state:new([{config, Config} | CommandLineConfig], Target)} + end. + +-spec convert_target([string()]) -> {ok, release | relup} | relcool:error(). +convert_target([]) -> + {ok, release}; +convert_target(["release"]) -> + {ok, release}; +convert_target(["relup"]) -> + {ok, relup}; +convert_target(Target) -> + ?RCL_ERROR({invalid_target, Target}). + +-spec validate_config(file:filename() | undefined) -> + {ok, file:filename() | undefined} | relcool:error(). +validate_config(undefined) -> + {ok, undefined}; +validate_config("") -> + {ok, undefined}; +validate_config(Config) -> + case filelib:is_regular(Config) of + true -> + {ok, filename:absname(Config)}; + false -> + ?RCL_ERROR({invalid_config_file, Config}) + end. + +-spec create_log([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_log(Opts, Acc) -> + LogLevel = proplists:get_value(log_level, Opts, 0), + if + LogLevel >= 0, LogLevel =< 2 -> + create_goals(Opts, [{log, rcl_log:new(LogLevel)} | Acc]); + true -> + ?RCL_ERROR({invalid_log_level, LogLevel}) + end. + +-spec create_goals([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_goals(Opts, Acc) -> + Goals = proplists:get_value(goals, Opts, []) ++ + proplists:get_all_values(goal, Opts), + case convert_goals(Goals, []) of + Error={error, _} -> + Error; + {ok, Specs} -> + create_overrides(Opts, [{goals, Specs} | Acc]) + end. + +-spec create_overrides([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_overrides(Opts, Acc) -> + Overrides = proplists:get_all_values(override, Opts) ++ + proplists:get_value(overrides, Opts, []), + case convert_overrides(Overrides, []) of + {ok, Overrides} -> + create_output_dir(Opts, [{overrides, Overrides} | Acc]); + Error -> + Error + end. + +-spec convert_overrides([{atom(), string() | binary()} | + string() | binary()], [{atom(), string() | binary()}]) -> + {ok, [string() | binary()]} | relcool:error(). +convert_overrides([], Acc) -> + {ok, Acc}; +convert_overrides([QA = {OverrideApp, _} | Rest], Acc) + when erlang:is_atom(OverrideApp) -> + convert_overrides(Rest, [QA | Acc]); +convert_overrides([Override | Rest], Acc) + when erlang:is_list(Override); erlang:is_binary(Override) -> + case re:split(Override, ":") of + [AppName, AppDir] -> + convert_overrides(Rest, [{erlang:iolist_to_binary(AppName), AppDir} | Acc]); + _ -> + ?RCL_ERROR({failed_to_parse_override, Override}) + end; +convert_overrides([QA | _], _) -> + ?RCL_ERROR({failed_to_parse_override, QA}). + +-spec convert_goals([string()], [rcl_depsolver:constraint()]) -> + {ok,[rcl_depsolver:constraint()]} | + relcool:error(). +convert_goals([], Specs) -> + %% Reverse the specs because order matters to rcl_depsolver + {ok, lists:reverse(Specs)}; +convert_goals([RawSpec | Rest], Acc) -> + parse_goal(RawSpec, Rest, Acc). + +-spec parse_goal(string() | binary() | rcl_depsolver:constraint(), + [string() | binary() | rcl_depsolver:constraint()], + rcl_depsolver:constraints()) -> + {ok, rcl_depsolver:constraints()} | relcool:error(). +parse_goal(Spec, Rest, Acc) + when erlang:is_atom(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(Spec, Rest, Acc) + when erlang:is_tuple(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(RawSpec, Rest, Acc) -> + case rcl_goal:parse(RawSpec) of + {ok, Spec} -> + convert_goals(Rest, [Spec | Acc]); + {fail, _} -> + ?RCL_ERROR({failed_to_parse, RawSpec}) + end. + +-spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_output_dir(Opts, Acc) -> + OutputDir = proplists:get_value(output_dir, Opts, "./_rel"), + create_lib_dirs(Opts, [{output_dir, filename:absname(OutputDir)} | Acc]). + +-spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_lib_dirs(Opts, Acc) -> + Dirs = proplists:get_all_values(lib_dir, Opts) ++ + proplists:get_value(lib_dirs, Opts, []), + case check_lib_dirs(Dirs) of + Error = {error, _} -> + Error; + ok -> + create_root_dir(Opts, [{lib_dirs, [filename:absname(Dir) || Dir <- Dirs]} | Acc]) + end. + +-spec create_root_dir([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_root_dir(Opts, Acc) -> + Dir = proplists:get_value(root_dir, Opts, undefined), + case Dir of + undefined -> + {ok, Cwd} = file:get_cwd(), + create_disable_default_libs(Opts, [{root_dir, Cwd} | Acc]); + _ -> + create_disable_default_libs(Opts, [{root_dir, Dir} | Acc]) + end. + +-spec create_disable_default_libs([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_disable_default_libs(Opts, Acc) -> + Def = proplists:get_value(disable_default_libs, Opts, false), + create_upfrom(Opts, [{disable_default_libs, Def} | Acc]). + +-spec create_upfrom([getopt:option()], rcl:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_upfrom(Opts, Acc) -> + case proplists:get_value(upfrom, Opts, undefined) of + undefined -> + create_caller(Opts, Acc); + UpFrom -> + create_caller(Opts, [{upfrom, UpFrom} | Acc]) + end. + +-spec create_caller([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_caller(Opts, Acc) -> + case proplists:get_value(caller, Opts, api) of + "command_line" -> + {ok, [{caller, command_line} | Acc]}; + "commandline" -> + {ok, [{caller, command_line} | Acc]}; + "api" -> + {ok, [{caller, api} | Acc]}; + api -> + {ok, [{caller, api} | Acc]}; + commandline -> + {ok, [{caller, command_line} | Acc]}; + command_line -> + {ok, [{caller, command_line} | Acc]}; + Caller -> + ?RCL_ERROR({invalid_caller, Caller}) + end. +-spec check_lib_dirs([string()]) -> ok | relcool:error(). +check_lib_dirs([]) -> + ok; +check_lib_dirs([Dir | Rest]) -> + case filelib:is_dir(Dir) of + false -> + ?RCL_ERROR({not_directory, Dir}); + true -> + check_lib_dirs(Rest) + end. diff --git a/src/rlx_depsolver.erl b/src/rlx_depsolver.erl new file mode 100644 index 0000000..0cf8c88 --- /dev/null +++ b/src/rlx_depsolver.erl @@ -0,0 +1,718 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%% ex: ts=4 sx=4 et +%% +%% Copyright 2012 Opscode, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% @author Eric Merritt +%% +%%%------------------------------------------------------------------- +%%% @doc +%%% This is a dependency constraint solver. You add your 'world' to the +%%% solver. That is the packages that exist, their versions and their +%%% dependencies. Then give the system a set of targets and ask it to solve. +%%% +%%% Lets say our world looks as follows +%%% +%%% app1 that has versions "0.1" +%%% depends on app3 any version greater then "0.2" +%%% "0.2" with no dependencies +%%% "0.3" with no dependencies +%%% +%%% app2 that has versions "0.1" with no dependencies +%%% "0.2" that depends on app3 exactly "0.3" +%%% "0.3" with no dependencies +%%% +%%% app3 that has versions +%%% "0.1", "0.2" and "0.3" all with no dependencies +%%% +%%% we can add this world to the system all at once as follows +%%% +%%% Graph0 = rcl_depsolver:new_graph(), +%%% Graph1 = rcl_depsolver:add_packages( +%%% [{app1, [{"0.1", [{app2, "0.2"}, +%%% {app3, "0.2", '>='}]}, +%%% {"0.2", []}, +%%% {"0.3", []}]}, +%%% {app2, [{"0.1", []}, +%%% {"0.2",[{app3, "0.3"}]}, +%%% {"0.3", []}]}, +%%% {app3, [{"0.1", []}, +%%% {"0.2", []}, +%%% {"0.3", []}]}]). +%%% +%%% We can also build it up incrementally using the other add_package and +%%% add_package_version functions. +%%% +%%% Finally, once we have built up the graph we can ask rcl_depsolver to solve the +%%% dependency constraints. That is to give us a list of valid dependencies by +%%% using the solve function. Lets say we want the app3 version "0.3" and all of +%%% its resolved dependencies. We could call solve as follows. +%%% +%%% rcl_depsolver:solve(Graph1, [{app3, "0.3"}]). +%%% +%%% That will give us the completely resolved dependencies including app3 +%%% itself. Lets be a little more flexible. Lets ask for a graph that is rooted +%%% in anything greater then or equal to app3 "0.3". We could do that by +%%% +%%% rcl_depsolver:solve(Graph1, [{app3, "0.3", '>='}]). +%%% +%%% Of course, you can specify any number of goals at the top level. +%%% @end +%%%------------------------------------------------------------------- +-module(rcl_depsolver). + +%% Public Api +-export([format_error/1, + format_roots/1, + format_culprits/1, + format_constraint/1, + format_version/1, + new_graph/0, + solve/2, + add_packages/2, + add_package/3, + add_package_version/3, + add_package_version/4, + parse_version/1, + is_valid_constraint/1, + filter_packages/2]). + +%% Internally Exported API. This should *not* be used outside of the rcl_depsolver +%% application. You have been warned. +-export([dep_pkg/1, + filter_package/2, + primitive_solve/3]). + +-export_type([t/0, + pkg/0, + constraint_op/0, + pkg_name/0, + vsn/0, + constraint/0, + dependency_set/0]). + +-export_type([dep_graph/0, constraints/0, + ordered_constraints/0, fail_info/0, + fail_detail/0]). +%%============================================================================ +%% type +%%============================================================================ +-type dep_graph() :: gb_tree(). +-opaque t() :: {?MODULE, dep_graph()}. +-type pkg() :: {pkg_name(), vsn()}. +-type pkg_name() :: binary() | atom(). +-type raw_vsn() :: ec_semver:any_version(). + +-type vsn() :: 'NO_VSN' + | ec_semver:semver(). + +-type constraint_op() :: + '=' | gte | '>=' | lte | '<=' + | gt | '>' | lt | '<' | pes | '~>' | between. + +-type raw_constraint() :: pkg_name() + | {pkg_name(), raw_vsn()} + | {pkg_name(), raw_vsn(), constraint_op()} + | {pkg_name(), raw_vsn(), vsn(), between}. + +-type constraint() :: pkg_name() + | {pkg_name(), vsn()} + | {pkg_name(), vsn(), constraint_op()} + | {pkg_name(), vsn(), vsn(), between}. + + +-type vsn_constraint() :: {raw_vsn(), [raw_constraint()]}. +-type dependency_set() :: {pkg_name(), [vsn_constraint()]}. + +%% Internal Types +-type constraints() :: [constraint()]. +-type ordered_constraints() :: [{pkg_name(), constraints()}]. +-type fail_info() :: {[pkg()], ordered_constraints()}. +-type fail_detail() :: {fail, [fail_info()]}. +-type version_checker() :: fun((vsn()) -> fail_detail() | vsn()). + +%%============================================================================ +%% API +%%============================================================================ +%% @doc create a new empty dependency graph +-spec new_graph() -> t(). +new_graph() -> + {?MODULE, gb_trees:empty()}. + +%% @doc add a complete set of list of packages to the graph. Where the package +%% consists of the name and a list of versions and dependencies. +%% +%% ``` rcl_depsolver:add_packages(Graph, +%% [{app1, [{"0.1", [{app2, "0.2"}, +%% {app3, "0.2", '>='}]}, +%% {"0.2", []}, +%% {"0.3", []}]}, +%% {app2, [{"0.1", []}, +%% {"0.2",[{app3, "0.3"}]}, +%% {"0.3", []}]}, +%% {app3, [{"0.1", []}, +%% {"0.2", []}, +%% {"0.3", []}]}]) +%% ''' +-spec add_packages(t(),[dependency_set()]) -> t(). +add_packages(Dom0, Info) + when is_list(Info) -> + lists:foldl(fun({Pkg, VsnInfo}, Dom1) -> + add_package(Dom1, Pkg, VsnInfo) + end, Dom0, Info). + +%% @doc add a single package to the graph, where it consists of a package name +%% and its versions and thier dependencies. +%% ```rcl_depsolver:add_package(Graph, app1, [{"0.1", [{app2, "0.2"}, +%% {app3, "0.2", '>='}]}, +%% {"0.2", []}, +%% {"0.3", []}]}]). +%% ''' +-spec add_package(t(),pkg_name(),[vsn_constraint()]) -> t(). +add_package(State, Pkg, Versions) + when is_list(Versions) -> + lists:foldl(fun({Vsn, Constraints}, Dom1) -> + add_package_version(Dom1, Pkg, Vsn, Constraints); + (Version, Dom1) -> + add_package_version(Dom1, Pkg, Version, []) + end, State, Versions). + +%% @doc add a set of dependencies to a specific package and version. +%% and its versions and thier dependencies. +%% ```rcl_depsolver:add_package(Graph, app1, "0.1", [{app2, "0.2"}, +%% {app3, "0.2", '>='}]}, +%% {"0.2", []}, +%% {"0.3", []}]). +%% ''' +-spec add_package_version(t(), pkg_name(), raw_vsn(), [raw_constraint()]) -> t(). +add_package_version({?MODULE, Dom0}, RawPkg, RawVsn, RawPkgConstraints) -> + Pkg = fix_pkg(RawPkg), + Vsn = parse_version(RawVsn), + %% Incoming constraints are raw + %% and need to be fixed + PkgConstraints = [fix_con(PkgConstraint) || + PkgConstraint <- RawPkgConstraints], + Info2 = + case gb_trees:lookup(Pkg, Dom0) of + {value, Info0} -> + case lists:keytake(Vsn, 1, Info0) of + {value, {Vsn, Constraints}, Info1} -> + [{Vsn, join_constraints(Constraints, + PkgConstraints)} + | Info1]; + false -> + [{Vsn, PkgConstraints} | Info0] + end; + none -> + [{Vsn, PkgConstraints}] + end, + {?MODULE, gb_trees:enter(Pkg, Info2, Dom0)}. + +%% @doc add a package and version to the dependency graph with no dependency +%% constraints, dependency constraints can always be added after the fact. +%% +%% ```rcl_depsolver:add_package_version(Graph, app1, "0.1").''' +-spec add_package_version(t(),pkg_name(),raw_vsn()) -> t(). +add_package_version(State, Pkg, Vsn) -> + add_package_version(State, Pkg, Vsn, []). + +%% @doc Given a set of goals (in the form of constrains) find a set of packages +%% and versions that satisfy all constraints. If no solution can be found then +%% an exception is thrown. +%% ``` rcl_depsolver:solve(State, [{app1, "0.1", '>='}]).''' +-spec solve(t(),[constraint()]) -> {ok, [pkg()]} | {error, term()}. +solve({?MODULE, DepGraph0}, RawGoals) + when erlang:length(RawGoals) > 0 -> + Goals = [fix_con(Goal) || Goal <- RawGoals], + case trim_unreachable_packages(DepGraph0, Goals) of + Error = {error, _} -> + Error; + DepGraph1 -> + case primitive_solve(DepGraph1, Goals, no_path) of + {fail, _} -> + [FirstCons | Rest] = Goals, + {error, rcl_depsolver_culprit:search(DepGraph1, [FirstCons], Rest)}; + Solution -> + {ok, Solution} + end + end. + +%% @doc Parse a string version into a tuple based version +-spec parse_version(raw_vsn() | vsn()) -> vsn(). +parse_version(RawVsn) + when erlang:is_list(RawVsn); + erlang:is_binary(RawVsn) -> + ec_semver:parse(RawVsn); +parse_version(Vsn) + when erlang:is_tuple(Vsn) -> + Vsn. + +%% @doc check that a specified constraint is a valid constraint. +-spec is_valid_constraint(constraint()) -> boolean(). +is_valid_constraint(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> + true; +is_valid_constraint({_Pkg, Vsn}) when is_tuple(Vsn) -> + true; +is_valid_constraint({_Pkg, Vsn, '='}) when is_tuple(Vsn) -> + true; +is_valid_constraint({_Pkg, _LVsn, gte}) -> + true; +is_valid_constraint({_Pkg, _LVsn, '>='}) -> + true; +is_valid_constraint({_Pkg, _LVsn, lte}) -> + true; +is_valid_constraint({_Pkg, _LVsn, '<='}) -> + true; +is_valid_constraint({_Pkg, _LVsn, gt}) -> + true; +is_valid_constraint({_Pkg, _LVsn, '>'}) -> + true; +is_valid_constraint({_Pkg, _LVsn, lt}) -> + true; +is_valid_constraint({_Pkg, _LVsn, '<'}) -> + true; +is_valid_constraint({_Pkg, _LVsn, pes}) -> + true; +is_valid_constraint({_Pkg, _LVsn, '~>'}) -> + true; +is_valid_constraint({_Pkg, _LVsn1, _LVsn2, between}) -> + true; +is_valid_constraint(_InvalidConstraint) -> + false. + + +%% @doc given a list of package name version pairs, and a list of constraints +%% return every member of that list that matches all constraints. +-spec filter_packages([{pkg_name(), raw_vsn()}], [raw_constraint()]) -> + {ok, [{pkg_name(), raw_vsn()}]} + | {error, Reason::term()}. +filter_packages(PVPairs, RawConstraints) -> + Constraints = [fix_con(Constraint) || Constraint <- RawConstraints], + case check_constraints(Constraints) of + ok -> + {ok, [PVPair || PVPair <- PVPairs, + filter_pvpair_by_constraint(fix_con(PVPair), Constraints)]}; + Error -> + Error + end. + +%% @doc Produce a full error message for a given error condition. This includes +%% details of the paths taken to resolve the dependencies and shows which dependencies +%% could not be satisfied +-spec format_error({error, {unreachable_package, list()} | + {invalid_constraints, [constraint()]} | + list()}) -> iolist(). +format_error(Error) -> + rcl_depsolver_culprit:format_error(Error). + +%% @doc Return a formatted list of roots of the dependency trees which +%% could not be satisified. These may also have versions attached. +%% Example: +%% +%% ```(foo = 1.2.0), bar``` +%% +-spec format_roots([constraints()]) -> iolist(). +format_roots(Roots) -> + rcl_depsolver_culprit:format_roots(Roots). + + +%% @doc Return a formatted list of the culprit depenedencies which led to +%% the dependencies not being satisfied. Example: +%% +%% ```(foo = 1.2.0) -> (bar > 2.0.0)``` +-spec format_culprits([{[constraint()], [constraint()]}]) -> iolist(). +format_culprits(Culprits) -> + rcl_depsolver_culprit:format_culprits(Culprits). + +%% @doc A formatted version tuple +-spec format_version(vsn()) -> iolist(). +format_version(Version) -> + rcl_depsolver_culprit:format_version(Version). + +%% @doc A formatted constraint tuple +-spec format_constraint(constraint()) -> iolist(). +format_constraint(Constraint) -> + rcl_depsolver_culprit:format_constraint(Constraint). + +%%==================================================================== +%% Internal Functions +%%==================================================================== +-spec check_constraints(constraints()) -> + ok | {error, {invalid_constraints, [term()]}}. +check_constraints(Constraints) -> + PossibleInvalids = + lists:foldl(fun(Constraint, InvalidConstraints) -> + case is_valid_constraint(Constraint) of + true -> + InvalidConstraints; + false -> + [Constraint | InvalidConstraints] + end + end, [], Constraints), + case PossibleInvalids of + [] -> + ok; + _ -> + {error, {invalid_constraints, PossibleInvalids}} + end. + + +-spec filter_pvpair_by_constraint({pkg_name(), vsn()}, [constraint()]) -> + boolean(). +filter_pvpair_by_constraint(PVPair, Constraints) -> + lists:all(fun(Constraint) -> + filter_package(PVPair, Constraint) + end, Constraints). + +-spec filter_package({pkg_name(), vsn()}, constraint()) -> + boolean(). +filter_package({PkgName, Vsn}, C = {PkgName, _}) -> + is_version_within_constraint(Vsn, C); +filter_package({PkgName, Vsn}, C = {PkgName, _, _}) -> + is_version_within_constraint(Vsn, C); +filter_package({PkgName, Vsn}, C = {PkgName, _, _, _}) -> + is_version_within_constraint(Vsn, C); +filter_package(_, _) -> + %% If its not explicitly excluded its included + true. + +%% @doc +%% fix the package name. If its a list turn it into a binary otherwise leave it as an atom +fix_pkg(Pkg) when is_list(Pkg) -> + erlang:list_to_binary(Pkg); +fix_pkg(Pkg) when is_binary(Pkg); is_atom(Pkg) -> + Pkg. + +%% @doc +%% fix package. Take a package with a possible invalid version and fix it. +-spec fix_con(raw_constraint()) -> constraint(). +fix_con({Pkg, Vsn}) -> + {fix_pkg(Pkg), parse_version(Vsn)}; +fix_con({Pkg, Vsn, CI}) -> + {fix_pkg(Pkg), parse_version(Vsn), CI}; +fix_con({Pkg, Vsn1, Vsn2, CI}) -> + {fix_pkg(Pkg), parse_version(Vsn1), + parse_version(Vsn2), CI}; +fix_con(Pkg) -> + fix_pkg(Pkg). + + +%% @doc given two lists of constraints join them in such a way that no +%% constraint is duplicated but the over all order of the constraints is +%% preserved. Order drives priority in this solver and is important for that +%% reason. +-spec join_constraints([constraint()], [constraint()]) -> + [constraint()]. +join_constraints(NewConstraints, ExistingConstraints) -> + ECSet = sets:from_list(ExistingConstraints), + FilteredNewConstraints = [NC || NC <- NewConstraints, + not sets:is_element(NC, ECSet)], + ExistingConstraints ++ FilteredNewConstraints. + + +%% @doc constraints is an associated list keeping track of all the constraints +%% that have been placed on a package +-spec new_constraints() -> constraints(). +new_constraints() -> + []. + +%% @doc Given a dep graph and a set of goals this either solves the problem or +%% fails. This is basically the root solver of the system, the main difference +%% from the exported solve/2 function is the fact that this does not do the +%% culprit search. +-spec primitive_solve(dep_graph(),[constraint()], term()) -> + [pkg()] | fail_detail(). +primitive_solve(State, PackageList, PathInd) + when erlang:length(PackageList) > 0 -> + Constraints = lists:foldl(fun(Info, Acc) -> + add_constraint('_GOAL_', 'NO_VSN', Acc, Info) + end, new_constraints(), PackageList), + + Pkgs = lists:map(fun dep_pkg/1, PackageList), + all_pkgs(State, [], Pkgs, Constraints, PathInd). + +%% @doc +%% given a Pkg | {Pkg, Vsn} | {Pkg, Vsn, Constraint} return Pkg +-spec dep_pkg(constraint()) -> pkg_name(). +dep_pkg({Pkg, _Vsn}) -> + Pkg; +dep_pkg({Pkg, _Vsn, _}) -> + Pkg; +dep_pkg({Pkg, _Vsn1, _Vsn2, _}) -> + Pkg; +dep_pkg(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> + Pkg. + +-spec add_constraint(pkg_name(), vsn(), [constraint()],constraint()) -> ordered_constraints(). +add_constraint(SrcPkg, SrcVsn, PkgsConstraints, PkgConstraint) -> + case is_valid_constraint(PkgConstraint) of + true -> ok; + false -> erlang:throw({invalid_constraint, PkgConstraint}) + end, + PkgName = dep_pkg(PkgConstraint), + Constraints1 = + case lists:keysearch(PkgName, 1, PkgsConstraints) of + false -> + []; + {value, {PkgName, Constraints0}} -> + Constraints0 + end, + [{PkgName, [{PkgConstraint, {SrcPkg, SrcVsn}} | Constraints1]} + | lists:keydelete(PkgName, 1, PkgsConstraints)]. + +%% @doc +%% Extend the currently active constraints correctly for the given constraints. +-spec extend_constraints(pkg_name(), vsn(), constraints(),constraints()) -> + [{pkg_name(), constraints()}]. +extend_constraints(SrcPkg, SrcVsn, ExistingConstraints0, NewConstraints) -> + lists:foldl(fun (Constraint, ExistingConstraints1) -> + add_constraint(SrcPkg, SrcVsn, ExistingConstraints1, Constraint) + end, + ExistingConstraints0, [{SrcPkg, SrcVsn} | NewConstraints]). + +-spec is_version_within_constraint(vsn(),constraint()) -> boolean(). +is_version_within_constraint(_Vsn, Pkg) when is_atom(Pkg) orelse is_binary(Pkg) -> + true; +is_version_within_constraint(Vsn, {_Pkg, NVsn}) -> + ec_semver:eql(Vsn, NVsn); +is_version_within_constraint(Vsn, {_Pkg, NVsn, '='}) -> + ec_semver:eql(Vsn, NVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, gte}) -> + ec_semver:gte(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, '>='}) -> + ec_semver:gte(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, lte}) -> + ec_semver:lte(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, '<='}) -> + ec_semver:lte(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, gt}) -> + ec_semver:gt(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, '>'}) -> + ec_semver:gt(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, lt}) -> + ec_semver:lt(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, '<'}) -> + ec_semver:lt(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, 'pes'}) -> + ec_semver:pes(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn, '~>'}) -> + ec_semver:pes(Vsn, LVsn); +is_version_within_constraint(Vsn, {_Pkg, LVsn1, LVsn2, between}) -> + ec_semver:between(LVsn1, LVsn2, Vsn); +is_version_within_constraint(_Vsn, _Pkg) -> + false. + +%% @doc +%% Get the currently active constraints that relate to the specified package +-spec get_constraints([{pkg_name(), constraints()}],pkg_name()) -> constraints(). +get_constraints(PkgsConstraints, PkgName) -> + case lists:keysearch(PkgName, 1, PkgsConstraints) of + false -> + []; + {value, {PkgName, Constraints}} -> + Constraints + end. + +%% @doc +%% Given a package name get the list of all versions available for that package. +-spec get_versions(dep_graph(),pkg_name()) -> [vsn()]. +get_versions(DepGraph, PkgName) -> + case gb_trees:lookup(PkgName, DepGraph) of + none -> + []; + {value, AllVsns} -> + [Vsn || {Vsn, _} <- AllVsns] + end. + +%% @doc +%% make sure a given name/vsn meets all current constraints +-spec valid_version(pkg_name(),vsn(),constraints()) -> boolean(). +valid_version(PkgName, Vsn, PkgConstraints) -> + lists:all(fun ({L, _ConstraintSrc}) -> + is_version_within_constraint(Vsn, L) + end, + get_constraints(PkgConstraints, PkgName)). + +%% @doc +%% Given a Package Name and a set of constraints get a list of package +%% versions that meet all constraints. +-spec constrained_package_versions(dep_graph(),pkg_name(),constraints()) -> + [vsn()]. +constrained_package_versions(State, PkgName, PkgConstraints) -> + Versions = get_versions(State, PkgName), + [Vsn || Vsn <- Versions, valid_version(PkgName, Vsn, PkgConstraints)]. + +%% Given a list of constraints filter said list such that only fail (for things +%% that do not match a package and pkg are returned. Since at the end only pkg() +%% we should have a pure list of packages. +-spec filter_package_constraints([constraint()]) -> fail | pkg(). +filter_package_constraints([]) -> + fail; +filter_package_constraints([PkgCon | PkgConstraints]) -> + case PkgCon of + {Pkg, _} when is_atom(Pkg); is_binary(Pkg) -> + filter_package_constraints(PkgConstraints); + {{_Pkg1, _Vsn} = PV, _} -> + PV; + {{_Pkg2, _Vsn, _R}, _} -> + filter_package_constraints(PkgConstraints); + {{_Pkg2, _Vsn1, _Vsn2, _R}, _} -> + filter_package_constraints(PkgConstraints) + end. + +%% @doc all_pkgs is one of the set of mutually recursive functions (all_pkgs and +%% pkgs) that serve to walk the solution space of dependency. +-spec all_pkgs(dep_graph(),[pkg()],[pkg_name()],[{pkg_name(), constraints()}], term()) -> + fail_detail() | [pkg()]. +all_pkgs(_State, Visited, [], Constraints, _PathInd) -> + PkgVsns = + [filter_package_constraints(PkgConstraints) + || {_, PkgConstraints} <- Constraints], + + %% PkgVsns should be a list of pkg() where all the constraints are correctly + %% met. If not we fail the solution. If so we return those pkg()s + case lists:all(fun({Pkg, Vsn}) -> + lists:all(fun({Constraint, _}) -> + is_version_within_constraint(Vsn, Constraint) + end, get_constraints(Constraints, Pkg)) + end, PkgVsns) of + true -> + PkgVsns; + false -> + {fail, [{Visited, Constraints}]} + end; +all_pkgs(State, Visited, [PkgName | PkgNames], Constraints, PathInd) + when is_atom(PkgName); is_binary(PkgName) -> + case lists:keymember(PkgName, 1, Visited) of + true -> + all_pkgs(State, Visited, PkgNames, Constraints, PathInd); + false -> + pkgs(State, Visited, PkgName, Constraints, PkgNames, PathInd) + end. + +%% @doc this is the key graph walker. Set of constraints it walks forward into +%% the solution space searching for a path that solves all dependencies. +-spec pkgs(dep_graph(),[pkg()], pkg_name(), [{pkg_name(), constraints()}], + [pkg_name()], term()) -> fail_detail() | [pkg()]. +pkgs(DepGraph, Visited, Pkg, Constraints, OtherPkgs, PathInd) -> + F = fun (Vsn) -> + Deps = get_dep_constraints(DepGraph, Pkg, Vsn), + UConstraints = extend_constraints(Pkg, Vsn, Constraints, Deps), + DepPkgs =[dep_pkg(Dep) || Dep <- Deps], + NewVisited = [{Pkg, Vsn} | Visited], + Res = all_pkgs(DepGraph, NewVisited, DepPkgs ++ OtherPkgs, UConstraints, PathInd), + Res + + end, + case constrained_package_versions(DepGraph, Pkg, Constraints) of + [] -> + {fail, [{Visited, Constraints}]}; + Res -> + lists_some(F, Res, PathInd) + end. + + +%% @doc This gathers the dependency constraints for a given package vsn from the +%% dependency graph. +-spec get_dep_constraints(dep_graph(), pkg_name(), vsn()) -> [constraint()]. +get_dep_constraints(DepGraph, PkgName, Vsn) -> + {Vsn, Constraints} = lists:keyfind(Vsn, 1, + gb_trees:get(PkgName, DepGraph)), + Constraints. + + +lists_some(F, Res, PathInd) -> + lists_some(F, Res, [], PathInd). + +-spec lists_some(version_checker(), [vsn()], term(), term()) -> vsn() | fail_detail(). +%% @doc lists_some is the root of the system the actual backtracing search that +%% makes the dep solver posible. It a takes a function that checks whether the +%% 'problem' has been solved and an fail indicator. As long as the evaluator +%% returns the fail indicator processing continues. If the evaluator returns +%% anything but the fail indicator that indicates success. +lists_some(_, [], FailPaths, _PathInd) -> + {fail, FailPaths}; +lists_some(F, [H | T], FailPaths, PathInd) -> + case F(H) of + {fail, FailPath} -> + case PathInd of + keep_paths -> + lists_some(F, T, [FailPath | FailPaths], PathInd); + _ -> + lists_some(F, T, [], PathInd) + end; + Res -> + Res + end. + +%% @doc given a graph and a set of top level goals return a graph that contains +%% only those top level packages and those packages that might be required by +%% those packages. +-spec trim_unreachable_packages(dep_graph(), [constraint()]) -> + dep_graph() | {error, term()}. +trim_unreachable_packages(State, Goals) -> + {_, NewState0} = new_graph(), + lists:foldl(fun(_Pkg, Error={error, _}) -> + Error; + (Pkg, NewState1) -> + PkgName = dep_pkg(Pkg), + find_reachable_packages(State, NewState1, PkgName) + end, NewState0, Goals). + +%% @doc given a list of versions and the constraints for that version rewrite +%% the new graph to reflect the requirements of those versions. +-spec rewrite_vsns(dep_graph(), dep_graph(), [{vsn(), [constraint()]}]) -> + dep_graph() | {error, term()}. +rewrite_vsns(ExistingGraph, NewGraph0, Info) -> + lists:foldl(fun(_, Error={error, _}) -> + Error; + ({_Vsn, Constraints}, NewGraph1) -> + lists:foldl(fun(_DepPkg, Error={error, _}) -> + Error; + (DepPkg, NewGraph2) -> + DepPkgName = dep_pkg(DepPkg), + find_reachable_packages(ExistingGraph, + NewGraph2, + DepPkgName) + end, NewGraph1, Constraints) + end, NewGraph0, Info). + +%% @doc Rewrite the existing dep graph removing anything that is not reachable +%% required by the goals or any of its potential dependencies. +-spec find_reachable_packages(dep_graph(), dep_graph(), pkg_name()) -> + dep_graph() | {error, term()}. +find_reachable_packages(_ExistingGraph, Error={error, _}, _PkgName) -> + Error; +find_reachable_packages(ExistingGraph, NewGraph0, PkgName) -> + case contains_package_version(NewGraph0, PkgName) of + true -> + NewGraph0; + false -> + case gb_trees:lookup(PkgName, ExistingGraph) of + {value, Info} -> + NewGraph1 = gb_trees:insert(PkgName, Info, NewGraph0), + rewrite_vsns(ExistingGraph, NewGraph1, Info); + none -> + {error, {unreachable_package, PkgName}} + end + end. + +%% @doc +%% Checks to see if a package name has been defined in the dependency graph +-spec contains_package_version(dep_graph(), pkg_name()) -> boolean(). +contains_package_version(Dom0, PkgName) -> + gb_trees:is_defined(PkgName, Dom0). diff --git a/src/rlx_depsolver_culprit.erl b/src/rlx_depsolver_culprit.erl new file mode 100644 index 0000000..f2115c7 --- /dev/null +++ b/src/rlx_depsolver_culprit.erl @@ -0,0 +1,359 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%% ex: ts=4 sx=4 et +%% +%% @author Eric Merritt +%% +%% Copyright 2012 Opscode, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%%%------------------------------------------------------------------- +%%% @doc +%%% This app does the culprit search for a failed solve. It searches +%%% through the goals provided by the user trying to find the first one +%%% that fails. It then returns that as the culprit along with the +%%% unknown apps from the goal, the version constrained apps from the +%%% goal, and the good apps (those not immediately constrained from +%%% the goals). +%%% @end +-module(rcl_depsolver_culprit). + +-export([search/3, + format_error/1, + format_version/1, + format_constraint/1, + format_roots/1, + format_culprits/1]). + +%%============================================================================ +%% Types +%%============================================================================ + +%%============================================================================ +%% Internal API +%%============================================================================ +%% @doc start running the solver, with each run reduce the number of constraints +%% set as goals. At some point the solver should succeed. +-spec search(rcl_depsolver:dep_graph(), [rcl_depsolver:constraint()], [rcl_depsolver:constraint()]) + -> term(). +search(State, ActiveCons, []) -> + case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of + {fail, FailPaths} -> + extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); + _Success -> + %% This should *never* happen. 'Culprit' above represents the last + %% possible constraint that could cause things to fail. There for + %% this should have failed as well. + inconsistant_graph_state + end; +search(State, ActiveCons, [NewCon | Constraints]) -> + case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of + {fail, FailPaths} -> + extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); + _Success -> + %% Move one constraint from the inactive to the active + %% constraints and run again + search(State, [NewCon | ActiveCons], Constraints) + end. + +format_error({error, {unreachable_package, AppName}}) -> + ["Dependency ", format_constraint(AppName), " is specified as a dependency ", + "but is not reachable by the system.\n"]; +format_error({error, {invalid_constraints, Constraints}}) -> + ["Invalid constraint ", add_s(Constraints), " specified ", + lists:foldl(fun(Con, "") -> + [io_lib:format("~p", [Con])]; + (Con, Acc) -> + [io_lib:format("~p", [Con]), ", " | Acc] + end, "", Constraints)]; +format_error({error, Detail}) -> + format_error(Detail); +format_error(Details) when erlang:is_list(Details) -> + ["Unable to solve constraints, the following solutions were attempted \n\n", + [[format_error_path(" ", Detail)] || Detail <- Details]]. + +-spec format_roots([rcl_depsolver:constraints()]) -> iolist(). +format_roots(Roots) -> + lists:foldl(fun(Root, Acc0) -> + lists:foldl( + fun(Con, "") -> + [format_constraint(Con)]; + (Con, Acc1) -> + [format_constraint(Con), ", " | Acc1] + end, Acc0, Root) + end, [], Roots). + +-spec format_culprits([{[rcl_depsolver:constraint()], [rcl_depsolver:constraint()]}]) -> iolist(). +format_culprits(FailingDeps) -> + Deps = sets:to_list(sets:from_list(lists:flatten([[rcl_depsolver:dep_pkg(Con) || Con <- Cons] + || {_, Cons} <- FailingDeps]))), + lists:foldl(fun(Con, "") -> + [format_constraint(Con)]; + (Con, Acc1) -> + [format_constraint(Con), + ", " | Acc1] + end, [], Deps). + +-spec format_version(rcl_depsolver:vsn()) -> iolist(). +format_version(Vsn) -> + ec_semver:format(Vsn). + +-spec format_constraint(rcl_depsolver:constraint()) -> list(). +format_constraint(Pkg) when is_atom(Pkg) -> + erlang:atom_to_list(Pkg); +format_constraint(Pkg) when is_binary(Pkg) -> + erlang:binary_to_list(Pkg); +format_constraint({Pkg, Vsn}) when is_tuple(Vsn) -> + ["(", format_constraint(Pkg), " = ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '='}) when is_tuple(Vsn) -> + ["(", format_constraint(Pkg), " = ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, gte}) -> + ["(", format_constraint(Pkg), " >= ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '>='}) -> + ["(", format_constraint(Pkg), " >= ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, lte}) -> + ["(", format_constraint(Pkg), " <= ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '<='}) -> + ["(", format_constraint(Pkg), " <= ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, gt}) -> + ["(", format_constraint(Pkg), " > ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '>'}) -> + ["(", format_constraint(Pkg), " > ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, lt}) -> + ["(", format_constraint(Pkg), " < ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '<'}) -> + ["(", format_constraint(Pkg), " < ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, pes}) -> + ["(", format_constraint(Pkg), " ~> ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn, '~>'}) -> + ["(", format_constraint(Pkg), " ~> ", + format_version(Vsn), ")"]; +format_constraint({Pkg, Vsn1, Vsn2, between}) -> + ["(", format_constraint(Pkg), " between ", + format_version(Vsn1), " and ", + format_version(Vsn2), ")"]. + + +%%============================================================================ +%% Internal Functions +%%============================================================================ +-spec append_value(term(), term(), proplists:proplist()) -> proplists:proplist(). +append_value(Key, Value, PropList) -> + case proplists:get_value(Key, PropList, undefined) of + undefined -> + [{Key, Value} | PropList]; + ExistingValue -> + [{Key, sets:to_list(sets:from_list([Value | ExistingValue]))} | + proplists:delete(Key, PropList)] + end. + +-spec strip_goal([[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]) -> + [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]. +strip_goal([{'_GOAL_', 'NO_VSN'}, Children]) -> + Children; +strip_goal(All = [Val | _]) + when erlang:is_list(Val) -> + [strip_goal(Element) || Element <- All]; +strip_goal(Else) -> + Else. + +-spec extract_culprit_information0(rcl_depsolver:constraints(), + [rcl_depsolver:fail_info()]) -> + [term()]. +extract_culprit_information0(ActiveCons, FailInfo) + when is_list(FailInfo) -> + [extract_culprit_information1(ActiveCons, FI) || FI <- FailInfo]. + + +-spec extract_root(rcl_depsolver:constraints(), [rcl_depsolver:pkg()]) -> + {[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}. +extract_root(ActiveCons, TPath = [PRoot | _]) -> + RootName = rcl_depsolver:dep_pkg(PRoot), + Roots = lists:filter(fun(El) -> + RootName =:= rcl_depsolver:dep_pkg(El) + end, ActiveCons), + {Roots, TPath}. + +-spec extract_culprit_information1(rcl_depsolver:constraints(), + rcl_depsolver:fail_info()) -> + term(). +extract_culprit_information1(_ActiveCons, {[], RawConstraints}) -> + %% In this case where there was no realized versions, the GOAL + %% constraints actually where unsatisfiable + Constraints = lists:flatten([Constraints || + {_, Constraints} <- RawConstraints]), + + Cons = [Pkg || {Pkg, Src} <- Constraints, + Src =:= {'_GOAL_', 'NO_VSN'}], + {[{Cons, Cons}], []}; +extract_culprit_information1(ActiveCons, {Path, RawConstraints}) -> + Constraints = lists:flatten([Constraints || + {_, Constraints} <- RawConstraints]), + FailCons = + lists:foldl(fun(El = {FailedPkg, FailedVsn}, Acc1) -> + case get_constraints(FailedPkg, FailedVsn, Path, + Constraints) of + [] -> + Acc1; + Cons -> + append_value(El, Cons, Acc1) + end + end, [], lists:reverse(Path)), + TreedPath = strip_goal(treeize_path({'_GOAL_', 'NO_VSN'}, Constraints, [])), + RunListItems = [extract_root(ActiveCons, TPath) || TPath <- TreedPath], + {RunListItems, FailCons}. + +-spec follow_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), + {rcl_depsolver:constraint(), rcl_depsolver:pkg()}) -> + false | {ok, rcl_depsolver:constraint()}. +follow_chain(Pkg, Vsn, {{Pkg, Vsn}, {Pkg, Vsn}}) -> + %% When the package version is the same as the source we dont want to try to follow it at all + false; +follow_chain(Pkg, Vsn, {Con, {Pkg, Vsn}}) -> + {ok, Con}; +follow_chain(_Pkg, _Vsn, _) -> + false. + +-spec find_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), + [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> + rcl_depsolver:constraints(). +find_chain(Pkg, Vsn, Constraints) -> + lists:foldl(fun(NCon, Acc) -> + case follow_chain(Pkg, Vsn, NCon) of + {ok, Con} -> + [Con | Acc]; + false -> + Acc + end + end, [], Constraints). + +-spec get_constraints(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), [rcl_depsolver:pkg()], + [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> + rcl_depsolver:constraints(). +get_constraints(FailedPkg, FailedVsn, Path, Constraints) -> + Chain = find_chain(FailedPkg, FailedVsn, Constraints), + lists:filter(fun(Con) -> + PkgName = rcl_depsolver:dep_pkg(Con), + (lists:any(fun(PathEl) -> + not rcl_depsolver:filter_package(PathEl, Con) + end, Path) orelse + not lists:keymember(PkgName, 1, Path)) + end, Chain). + +-spec pkg_vsn(rcl_depsolver:constraint(), [{rcl_depsolver:constraint(), + rcl_depsolver:pkg()}]) -> + [rcl_depsolver:pkg()]. +pkg_vsn(PkgCon, Constraints) -> + PkgName = rcl_depsolver:dep_pkg(PkgCon), + [DepPkg || Con = {DepPkg, _} <- Constraints, + case Con of + {Pkg = {PkgName, PkgVsn}, {PkgName, PkgVsn}} -> + rcl_depsolver:filter_package(Pkg, PkgCon); + _ -> + false + end]. + +-spec depends(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), + rcl_depsolver:pkg()}], + [rcl_depsolver:pkg()]) -> + [rcl_depsolver:pkg()]. +depends(SrcPkg, Constraints, Seen) -> + lists:flatten([pkg_vsn(Pkg, Constraints) || {Pkg, Source} <- Constraints, + Source =:= SrcPkg andalso + Pkg =/= SrcPkg andalso + not lists:member(Pkg, Seen)]). + +-spec treeize_path(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), + rcl_depsolver:pkg()}], + [rcl_depsolver:pkg()]) -> + [rcl_depsolver:pkg() | [rcl_depsolver:pkg()]]. +treeize_path(Pkg, Constraints, Seen0) -> + Seen1 = [Pkg | Seen0], + case depends(Pkg, Constraints, Seen1) of + [] -> + [Pkg]; + Deps -> + [Pkg, [treeize_path(Dep, Constraints, Seen1) || + Dep <- Deps]] + + end. + +-spec add_s(list()) -> iolist(). +add_s(Roots) -> + case erlang:length(Roots) of + Len when Len > 1 -> + "s"; + _ -> + "" + end. + +-spec format_path(string(), [rcl_depsolver:pkg()]) -> iolist(). +format_path(CurrentIdent, Path) -> + [CurrentIdent, " ", + lists:foldl(fun(Con, "") -> + [format_constraint(Con)]; + (Con, Acc) -> + [format_constraint(Con), " -> " | Acc] + end, "", Path), + "\n"]. + +-spec format_dependency_paths(string(), [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()], + [{rcl_depsolver:pkg(), [rcl_depsolver:constraint()]}], [rcl_depsolver:pkg()]) -> iolist(). +format_dependency_paths(CurrentIndent, [SubPath | Rest], FailingDeps, Acc) + when erlang:is_list(SubPath) -> + [format_dependency_paths(CurrentIndent, lists:sort(SubPath), FailingDeps, Acc), + format_dependency_paths(CurrentIndent, Rest, FailingDeps, Acc)]; +format_dependency_paths(CurrentIndent, [Dep], FailingDeps, Acc) + when erlang:is_tuple(Dep) -> + case proplists:get_value(Dep, FailingDeps, undefined) of + undefined -> + format_path(CurrentIndent, [Dep | Acc]); + Cons -> + [format_path(CurrentIndent, [Con, Dep | Acc]) || Con <- Cons] + end; +format_dependency_paths(CurrentIndent, [Dep | Rest], FailingDeps, Acc) + when erlang:is_tuple(Dep) -> + case proplists:get_value(Dep, FailingDeps, undefined) of + undefined -> + format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Dep | Acc]); + Cons -> + [[format_path(CurrentIndent, [Con, Dep | Acc]) || Con <- Cons], + format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Dep | Acc])] + end; +format_dependency_paths(CurrentIndent, [Con | Rest], FailingDeps, Acc) -> + format_dependency_paths(CurrentIndent, Rest, FailingDeps, [Con | Acc]); +format_dependency_paths(_CurrentIndent, [], _FailingDeps, _Acc) -> + []. + +-spec format_error_path(string(), {[{[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}], + [rcl_depsolver:constraint()]}) -> iolist(). +format_error_path(CurrentIndent, {RawPaths, FailingDeps}) -> + Roots = [RootSet || {RootSet, _} <- RawPaths], + Paths = [Path || {_, Path} <- RawPaths], + [CurrentIndent, "Unable to satisfy goal constraint", + add_s(Roots), " ", format_roots(Roots), " due to constraint", add_s(FailingDeps), " on ", + format_culprits(FailingDeps), "\n", + format_dependency_paths(CurrentIndent, lists:sort(Paths), FailingDeps, []), ""]. diff --git a/src/rlx_dscv_util.erl b/src/rlx_dscv_util.erl new file mode 100644 index 0000000..68dcb68 --- /dev/null +++ b/src/rlx_dscv_util.erl @@ -0,0 +1,142 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provider uses the lib_dir setting of the state. It searches the +%%% Lib Dirs looking for all OTP Applications that are available. When it finds +%%% those OTP Applications it loads the information about them and adds them to +%%% the state of available apps. This implements the rcl_provider behaviour. +-module(rcl_dscv_util). + +-export([do/2, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% Types +%%============================================================================ + +-type process_fun(Result) :: fun((filename:name(), file | directory) -> + {ok, Result} | + {error, term()} | + {ok, Result, Recurse::boolean()} | + {noresult, Recurse::boolean()} | + {error, term()}). + +%%============================================================================ +%% API +%%============================================================================ + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(process_fun([term()] | term()), [filename:name()]) -> + [term() | {error, term()}]. +do(ProcessDir, LibDirs) -> + lists:flatten(ec_plists:map(fun(LibDir) -> + discover_dir(ProcessDir, LibDir, + ec_file:type(LibDir)) + end, LibDirs)). + +-spec format_error([ErrorDetail::term()]) -> iolist(). +format_error(ErrorDetails) + when erlang:is_list(ErrorDetails) -> + [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({accessing, File, eaccess}) -> + io_lib:format("permission denied accessing file ~s", [File]); +format_detail({accessing, File, Type}) -> + io_lib:format("error (~p) accessing file ~s", [Type, File]); +format_detail({not_a_directory, EbinDir}) -> + io_lib:format("~s is not a directory when it should be a directory", [EbinDir]). + +-spec discover_dir(process_fun([term()] | term()), + file:name(), + directory | file | symlink) -> + [{ok, term()} + | {error, Reason::term()}] + | [] + | {ok, term()} + | {error, Reason::term()}. +discover_dir(ProcessDir, File, directory) -> + case ProcessDir(File, directory) of + {ok, Result, true} -> + [{ok, Result} | recurse(ProcessDir, File)]; + {noresult, true} -> + recurse(ProcessDir, File); + {ok, Result, _} -> + [{ok, Result}]; + {noresult, _} -> + []; + Err = {error, _} -> + [Err] + end; +discover_dir(ProcessDir, File, file) -> + case ProcessDir(File, file) of + {ok, Result} -> + [{ok, Result}]; + {noresult, _} -> + []; + Err = {error, _} -> + [Err] + end; +discover_dir(ProcessDir, File, symlink) -> + case filelib:is_dir(File) of + false -> + discover_dir(ProcessDir, File, file); + true -> + discover_real_symlink_dir(ProcessDir, File) + end. + +discover_real_symlink_dir(ProcessDir, File) -> + {ok, CurCwd} = file:get_cwd(), + ok = file:set_cwd(File), + {ok, ActualRealDir} = file:get_cwd(), + ok = file:set_cwd(CurCwd), + lists:prefix(iolist_to_list(filename:absname(ActualRealDir)), + iolist_to_list(filename:absname(File))), + case ProcessDir(File, directory) of + {ok, Result, true} -> + [{ok, Result} | recurse(ProcessDir, File)]; + {noresult, true} -> + recurse(ProcessDir, File); + {ok, Result, _} -> + [{ok, Result}]; + {noresult, _} -> + []; + Err = {error, _} -> + [Err] + end. + +recurse(ProcessDir, File) -> + case file:list_dir(File) of + {error, Reason} -> + {error, {accessing, File, Reason}}; + {ok, List} -> + ec_plists:map(fun(LibDir) -> + discover_dir(ProcessDir, LibDir, ec_file:type(LibDir)) + end, [filename:join([File, Dir]) || Dir <- List]) + end. + +iolist_to_list(IoList) -> + erlang:binary_to_list(erlang:iolist_to_binary(IoList)). diff --git a/src/rlx_goal.erl b/src/rlx_goal.erl new file mode 100644 index 0000000..bd3f265 --- /dev/null +++ b/src/rlx_goal.erl @@ -0,0 +1,263 @@ +-module(rlx_goal). +-export([parse/1,file/1]). +-compile({nowarn_unused_function,[p/4, p/5, p_eof/0, p_optional/1, p_not/1, p_assert/1, p_seq/1, p_and/1, p_choose/1, p_zero_or_more/1, p_one_or_more/1, p_label/2, p_string/1, p_anything/0, p_charclass/1, p_regexp/1, p_attempt/4, line/1, column/1]}). + + +-compile(export_all). +-spec file(file:name()) -> any(). +file(Filename) -> {ok, Bin} = file:read_file(Filename), parse(Bin). + +-spec parse(binary() | list()) -> any(). +parse(List) when is_list(List) -> parse(list_to_binary(List)); +parse(Input) when is_binary(Input) -> + setup_memo(), + Result = case 'constraint'(Input,{{line,1},{column,1}}) of + {AST, <<>>, _Index} -> AST; + Any -> Any + end, + release_memo(), Result. + +'constraint'(Input, Index) -> + p(Input, Index, 'constraint', fun(I,D) -> (p_choose([p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'between_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_string(<<",">>), p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'constraint_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), p_not(p_anything())])]))(I,D) end, fun(Node, _Idx) -> + case Node of + [_,AppName,_, _] -> + {ok, AppName}; + [_,AppName,_,Op,_,Vsn,_, _] -> + {ok, + {AppName, + rcl_goal_utils:to_vsn(Vsn), + rcl_goal_utils:to_op(Op)}}; + [_,AppName,_,Op,_,Vsn1,_,_,_,Vsn2,_,_] -> + {ok, + {AppName, + rcl_goal_utils:to_vsn(Vsn1), + rcl_goal_utils:to_vsn(Vsn2), + rcl_goal_utils:to_op(Op)}}; + _ -> + io:format("~p~n", [Node]) + end + end). + +'ws'(Input, Index) -> + p(Input, Index, 'ws', fun(I,D) -> (p_charclass(<<"[\s\t\n\s\r]">>))(I,D) end, fun(Node, Idx) -> transform('ws', Node, Idx) end). + +'app_name'(Input, Index) -> + p(Input, Index, 'app_name', fun(I,D) -> (p_one_or_more(p_charclass(<<"[a-zA-Z0-9_]">>)))(I,D) end, fun(Node, _Idx) -> erlang:list_to_atom(erlang:binary_to_list(erlang:iolist_to_binary(Node))) end). + +'between_op'(Input, Index) -> + p(Input, Index, 'between_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"btwn">>), p_string(<<"between">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of + [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); + _ -> Node + end + end). + +'constraint_op'(Input, Index) -> + p(Input, Index, 'constraint_op', fun(I,D) -> (p_choose([p_string(<<"=">>), p_string(<<"-">>), p_string(<<"<=">>), p_string(<<"<">>), p_string(<<"~>">>), p_string(<<">=">>), p_string(<<">">>), fun 'word_constraint_op'/2, p_string(<<":">>)]))(I,D) end, fun(Node, Idx) -> transform('constraint_op', Node, Idx) end). + +'word_constraint_op'(Input, Index) -> + p(Input, Index, 'word_constraint_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"gte">>), p_string(<<"lte">>), p_string(<<"gt">>), p_string(<<"lt">>), p_string(<<"pes">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of + [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); + _ -> Node + end + end). + +'version'(Input, Index) -> + p(Input, Index, 'version', fun(I,D) -> (p_one_or_more(p_charclass(<<"[0-9a-zA-Z-+.]">>)))(I,D) end, fun(Node, Idx) -> transform('version', Node, Idx) end). + + +transform(_,Node,_Index) -> Node. + +p(Inp, Index, Name, ParseFun) -> + p(Inp, Index, Name, ParseFun, fun(N, _Idx) -> N end). + +p(Inp, StartIndex, Name, ParseFun, TransformFun) -> + case get_memo(StartIndex, Name) of % See if the current reduction is memoized + {ok, Memo} -> %Memo; % If it is, return the stored result + Memo; + _ -> % If not, attempt to parse + Result = case ParseFun(Inp, StartIndex) of + {fail,_} = Failure -> % If it fails, memoize the failure + Failure; + {Match, InpRem, NewIndex} -> % If it passes, transform and memoize the result. + Transformed = TransformFun(Match, StartIndex), + {Transformed, InpRem, NewIndex} + end, + memoize(StartIndex, Name, Result), + Result + end. + +setup_memo() -> + put({parse_memo_table, ?MODULE}, ets:new(?MODULE, [set])). + +release_memo() -> + ets:delete(memo_table_name()). + +memoize(Index, Name, Result) -> + Memo = case ets:lookup(memo_table_name(), Index) of + [] -> []; + [{Index, Plist}] -> Plist + end, + ets:insert(memo_table_name(), {Index, [{Name, Result}|Memo]}). + +get_memo(Index, Name) -> + case ets:lookup(memo_table_name(), Index) of + [] -> {error, not_found}; + [{Index, Plist}] -> + case proplists:lookup(Name, Plist) of + {Name, Result} -> {ok, Result}; + _ -> {error, not_found} + end + end. + +memo_table_name() -> + get({parse_memo_table, ?MODULE}). + +p_eof() -> + fun(<<>>, Index) -> {eof, [], Index}; + (_, Index) -> {fail, {expected, eof, Index}} end. + +p_optional(P) -> + fun(Input, Index) -> + case P(Input, Index) of + {fail,_} -> {[], Input, Index}; + {_, _, _} = Success -> Success + end + end. + +p_not(P) -> + fun(Input, Index)-> + case P(Input,Index) of + {fail,_} -> + {[], Input, Index}; + {Result, _, _} -> {fail, {expected, {no_match, Result},Index}} + end + end. + +p_assert(P) -> + fun(Input,Index) -> + case P(Input,Index) of + {fail,_} = Failure-> Failure; + _ -> {[], Input, Index} + end + end. + +p_and(P) -> + p_seq(P). + +p_seq(P) -> + fun(Input, Index) -> + p_all(P, Input, Index, []) + end. + +p_all([], Inp, Index, Accum ) -> {lists:reverse( Accum ), Inp, Index}; +p_all([P|Parsers], Inp, Index, Accum) -> + case P(Inp, Index) of + {fail, _} = Failure -> Failure; + {Result, InpRem, NewIndex} -> p_all(Parsers, InpRem, NewIndex, [Result|Accum]) + end. + +p_choose(Parsers) -> + fun(Input, Index) -> + p_attempt(Parsers, Input, Index, none) + end. + +p_attempt([], _Input, _Index, Failure) -> Failure; +p_attempt([P|Parsers], Input, Index, FirstFailure)-> + case P(Input, Index) of + {fail, _} = Failure -> + case FirstFailure of + none -> p_attempt(Parsers, Input, Index, Failure); + _ -> p_attempt(Parsers, Input, Index, FirstFailure) + end; + Result -> Result + end. + +p_zero_or_more(P) -> + fun(Input, Index) -> + p_scan(P, Input, Index, []) + end. + +p_one_or_more(P) -> + fun(Input, Index)-> + Result = p_scan(P, Input, Index, []), + case Result of + {[_|_], _, _} -> + Result; + _ -> + {fail, {expected, Failure, _}} = P(Input,Index), + {fail, {expected, {at_least_one, Failure}, Index}} + end + end. + +p_label(Tag, P) -> + fun(Input, Index) -> + case P(Input, Index) of + {fail,_} = Failure -> + Failure; + {Result, InpRem, NewIndex} -> + {{Tag, Result}, InpRem, NewIndex} + end + end. + +p_scan(_, [], Index, Accum) -> {lists:reverse( Accum ), [], Index}; +p_scan(P, Inp, Index, Accum) -> + case P(Inp, Index) of + {fail,_} -> {lists:reverse(Accum), Inp, Index}; + {Result, InpRem, NewIndex} -> p_scan(P, InpRem, NewIndex, [Result | Accum]) + end. + +p_string(S) when is_list(S) -> p_string(list_to_binary(S)); +p_string(S) -> + Length = erlang:byte_size(S), + fun(Input, Index) -> + try + <> = Input, + {S, Rest, p_advance_index(S, Index)} + catch + error:{badmatch,_} -> {fail, {expected, {string, S}, Index}} + end + end. + +p_anything() -> + fun(<<>>, Index) -> {fail, {expected, any_character, Index}}; + (Input, Index) when is_binary(Input) -> + <> = Input, + {<>, Rest, p_advance_index(<>, Index)} + end. + +p_charclass(Class) -> + {ok, RE} = re:compile(Class, [unicode, dotall]), + fun(Inp, Index) -> + case re:run(Inp, RE, [anchored]) of + {match, [{0, Length}|_]} -> + {Head, Tail} = erlang:split_binary(Inp, Length), + {Head, Tail, p_advance_index(Head, Index)}; + _ -> {fail, {expected, {character_class, binary_to_list(Class)}, Index}} + end + end. + +p_regexp(Regexp) -> + {ok, RE} = re:compile(Regexp, [unicode, dotall, anchored]), + fun(Inp, Index) -> + case re:run(Inp, RE) of + {match, [{0, Length}|_]} -> + {Head, Tail} = erlang:split_binary(Inp, Length), + {Head, Tail, p_advance_index(Head, Index)}; + _ -> {fail, {expected, {regexp, binary_to_list(Regexp)}, Index}} + end + end. + +line({{line,L},_}) -> L; +line(_) -> undefined. + +column({_,{column,C}}) -> C; +column(_) -> undefined. + +p_advance_index(MatchedInput, Index) when is_list(MatchedInput) orelse is_binary(MatchedInput)-> % strings + lists:foldl(fun p_advance_index/2, Index, unicode:characters_to_list(MatchedInput)); +p_advance_index(MatchedInput, Index) when is_integer(MatchedInput) -> % single characters + {{line, Line}, {column, Col}} = Index, + case MatchedInput of + $\n -> {{line, Line+1}, {column, 1}}; + _ -> {{line, Line}, {column, Col+1}} + end. diff --git a/src/rlx_goal.peg b/src/rlx_goal.peg new file mode 100644 index 0000000..bfac607 --- /dev/null +++ b/src/rlx_goal.peg @@ -0,0 +1,55 @@ + +constraint <- ws? app_name ws? between_op ws? version ws? "," ws? version ws? !. + / ws? app_name ws? constraint_op ws? version ws? !. + / ws? app_name ws? !. + + ` + case Node of + [_,AppName,_, _] -> + {ok, AppName}; + [_,AppName,_,Op,_,Vsn,_, _] -> + {ok, + {AppName, + rcl_goal_utils:to_vsn(Vsn), + rcl_goal_utils:to_op(Op)}}; + [_,AppName,_,Op,_,Vsn1,_,_,_,Vsn2,_,_] -> + {ok, + {AppName, + rcl_goal_utils:to_vsn(Vsn1), + rcl_goal_utils:to_vsn(Vsn2), + rcl_goal_utils:to_op(Op)}}; + _ -> + io:format("~p~n", [Node]) + end + ` ; + +ws <- [ \t\n\s\r] ; + +app_name <- [a-zA-Z0-9_]+ + ` erlang:list_to_atom(erlang:binary_to_list(erlang:iolist_to_binary(Node))) ` ; + +between_op <- + ":" ws? ( "btwn" / "between" ) ws? ":" + ` case Node of + [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); + _ -> Node + end + ` ; + +constraint_op <- "=" / "-" / "<=" / "<" / "~>" / ">=" / ">" / word_constraint_op / ":" ; + +word_constraint_op <- + ":" ws? ( "gte" / "lte" / "gt" / "lt" / "pes" ) ws? ":" + ` case Node of + [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); + _ -> Node + end + ` ; + + +version <- [0-9a-zA-Z-+.]+ ; + +%% This only exists to get around a bug in erlang where if +%% warnings_as_errors is specified `nowarn` directives are ignored + + `-compile(export_all).` \ No newline at end of file diff --git a/src/rlx_goal_utils.erl b/src/rlx_goal_utils.erl new file mode 100644 index 0000000..6065e5c --- /dev/null +++ b/src/rlx_goal_utils.erl @@ -0,0 +1,77 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc +%%% Utilities to help with parsing of target specs +-module(rcl_goal_utils). + +-export([to_op/1, + to_vsn/1]). + +%%============================================================================ +%% types +%%============================================================================ + +%%============================================================================ +%% API +%%============================================================================ +-spec to_op(iolist()) -> rcl_depsolver:constraint_op(). +to_op(List) + when erlang:is_list(List) -> + to_op(erlang:iolist_to_binary(List)); +to_op(<<"=">>) -> + '='; +to_op(<<":">>) -> + '='; +to_op(<<"-">>) -> + '='; +to_op(<<"<">>) -> + 'lt'; +to_op(<<":lt:">>) -> + 'lt'; +to_op(<<"<=">>) -> + 'lte'; +to_op(<<":lte:">>) -> + 'lte'; +to_op(<<">">>) -> + 'gt'; +to_op(<<":gt:">>) -> + 'gt'; +to_op(<<">=">>) -> + 'gte'; +to_op(<<":gte:">>) -> + 'gte'; +to_op(<<"~>">>) -> + 'pes'; +to_op(<<":pes:">>) -> + 'pes'; +to_op(<<":btwn:">>) -> + 'between'; +to_op(<<":between:">>) -> + 'between'. + +to_vsn(Version) when erlang:is_list(Version) -> + to_vsn(erlang:iolist_to_binary(Version)); +to_vsn(Vsn) -> + rcl_depsolver:parse_version(Vsn). + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== diff --git a/src/rlx_log.erl b/src/rlx_log.erl new file mode 100644 index 0000000..71c0b5d --- /dev/null +++ b/src/rlx_log.erl @@ -0,0 +1,204 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provides simple output functions for relcool. You should use this +%%% to talk to the users if you are wrting code for the system +-module(rcl_log). + +-export([new/1, + log/4, + should/2, + debug/2, + debug/3, + info/2, + info/3, + error/2, + error/3, + log_level/1, + atom_log_level/1, + format/1]). + +-export_type([int_log_level/0, + atom_log_level/0, + log_level/0, + log_fun/0, + t/0]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% types +%%============================================================================ + +-type log_level() :: int_log_level() | atom_log_level(). + +-type int_log_level() :: 0..2. + +%% Why no warn? because for our purposes there is no difference between error +%% and warn +-type atom_log_level() :: error | info | debug. + +-opaque t() :: {?MODULE, int_log_level()}. + +-type log_fun() :: fun(() -> iolist()). + +%%============================================================================ +%% API +%%============================================================================ +%% @doc Create a new 'log level' for the system +-spec new(log_level()) -> t(). +new(LogLevel) when LogLevel >= 0, LogLevel =< 2 -> + {?MODULE, LogLevel}; +new(AtomLogLevel) + when AtomLogLevel =:= error; + AtomLogLevel =:= info; + AtomLogLevel =:= debug -> + LogLevel = case AtomLogLevel of + error -> 0; + info -> 1; + debug -> 2 + end, + new(LogLevel). + + +%% @doc log at the debug level given the current log state with a string or +%% function that returns a string +-spec debug(t(), string() | log_fun()) -> ok. +debug(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?RCL_DEBUG, Fun); +debug(LogState, String) -> + debug(LogState, "~s~n", [String]). + +%% @doc log at the debug level given the current log state with a format string +%% and argements @see io:format/2 +-spec debug(t(), string(), [any()]) -> ok. +debug(LogState, FormatString, Args) -> + log(LogState, ?RCL_DEBUG, FormatString, Args). + +%% @doc log at the info level given the current log state with a string or +%% function that returns a string +-spec info(t(), string() | log_fun()) -> ok. +info(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?RCL_INFO, Fun); +info(LogState, String) -> + info(LogState, "~s~n", [String]). + +%% @doc log at the info level given the current log state with a format string +%% and argements @see io:format/2 +-spec info(t(), string(), [any()]) -> ok. +info(LogState, FormatString, Args) -> + log(LogState, ?RCL_INFO, FormatString, Args). + +%% @doc log at the error level given the current log state with a string or +%% format string that returns a function +-spec error(t(), string() | log_fun()) -> ok. +error(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?RCL_ERROR, Fun); +error(LogState, String) -> + error(LogState, "~s~n", [String]). + +%% @doc log at the error level given the current log state with a format string +%% and argements @see io:format/2 +-spec error(t(), string(), [any()]) -> ok. +error(LogState, FormatString, Args) -> + log(LogState, ?RCL_ERROR, FormatString, Args). + +%% @doc Execute the fun passed in if log level is as expected. +-spec log(t(), int_log_level(), log_fun()) -> ok. +log({?MODULE, DetailLogLevel}, LogLevel, Fun) + when DetailLogLevel >= LogLevel -> + io:format("~s~n", [Fun()]); +log(_, _, _) -> + ok. + + +%% @doc when the module log level is less then or equal to the log level for the +%% call then write the log info out. When its not then ignore the call. +-spec log(t(), int_log_level(), string(), [any()]) -> ok. +log({?MODULE, DetailLogLevel}, LogLevel, FormatString, Args) + when DetailLogLevel >= LogLevel, + erlang:is_list(Args) -> + io:format(FormatString, Args); +log(_, _, _, _) -> + ok. + +%% @doc return a boolean indicating if the system should log for the specified +%% levelg +-spec should(t(), int_log_level() | any()) -> boolean(). +should({?MODULE, DetailLogLevel}, LogLevel) + when DetailLogLevel >= LogLevel -> + true; +should(_, _) -> + false. + +%% @doc get the current log level as an integer +-spec log_level(t()) -> int_log_level(). +log_level({?MODULE, DetailLogLevel}) -> + DetailLogLevel. + +%% @doc get the current log level as an atom +-spec atom_log_level(t()) -> atom_log_level(). +atom_log_level({?MODULE, ?RCL_ERROR}) -> + error; +atom_log_level({?MODULE, ?RCL_INFO}) -> + info; +atom_log_level({?MODULE, ?RCL_DEBUG}) -> + debug. + +-spec format(t()) -> iolist(). +format(Log) -> + [<<"(">>, + erlang:integer_to_list(log_level(Log)), <<":">>, + erlang:atom_to_list(atom_log_level(Log)), + <<")">>]. + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +should_test() -> + ErrorLogState = new(error), + ?assertMatch(true, should(ErrorLogState, ?RCL_ERROR)), + ?assertMatch(true, not should(ErrorLogState, ?RCL_INFO)), + ?assertMatch(true, not should(ErrorLogState, ?RCL_DEBUG)), + ?assertEqual(?RCL_ERROR, log_level(ErrorLogState)), + ?assertEqual(error, atom_log_level(ErrorLogState)), + + InfoLogState = new(info), + ?assertMatch(true, should(InfoLogState, ?RCL_ERROR)), + ?assertMatch(true, should(InfoLogState, ?RCL_INFO)), + ?assertMatch(true, not should(InfoLogState, ?RCL_DEBUG)), + ?assertEqual(?RCL_INFO, log_level(InfoLogState)), + ?assertEqual(info, atom_log_level(InfoLogState)), + + DebugLogState = new(debug), + ?assertMatch(true, should(DebugLogState, ?RCL_ERROR)), + ?assertMatch(true, should(DebugLogState, ?RCL_INFO)), + ?assertMatch(true, should(DebugLogState, ?RCL_DEBUG)), + ?assertEqual(?RCL_DEBUG, log_level(DebugLogState)), + ?assertEqual(debug, atom_log_level(DebugLogState)). + +-endif. diff --git a/src/rlx_provider.erl b/src/rlx_provider.erl new file mode 100644 index 0000000..4d8f044 --- /dev/null +++ b/src/rlx_provider.erl @@ -0,0 +1,117 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright 2011 Erlware, LLC. +%%% @doc +%%% A module that supports providing state manipulation services to the system. +%%% @end +%%%------------------------------------------------------------------- +-module(rcl_provider). + +%% API +-export([new/2, + do/2, + impl/1, + format_error/1, + format_error/2, + format/1]). + +-export_type([t/0]). + +-include_lib("relcool/include/relcool.hrl"). + +%%%=================================================================== +%%% Types +%%%=================================================================== + +-opaque t() :: {?MODULE, module()}. + + +-ifdef(have_callback_support). + +-callback init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-callback do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-callback format_error(Reason::term()) -> iolist(). + +-else. + +%% In the case where R14 or lower is being used to compile the system +%% we need to export a behaviour info +-export([behaviour_info/1]). +-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. +behaviour_info(callbacks) -> + [{init, 1}, + {do, 1}, + {format_error, 1}]; +behaviour_info(_) -> + undefined. + +-endif. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc create a new provider object from the specified module. The +%% module should implement the provider behaviour. +%% +%% @param ModuleName The module name. +%% @param State0 The current state of the system +-spec new(module(), rcl_state:t()) -> + {t(), {ok, rcl_state:t()}} | relcool:error(). +new(ModuleName, State0) when is_atom(ModuleName) -> + State1 = ModuleName:init(State0), + case code:which(ModuleName) of + non_existing -> + ?RCL_ERROR({non_existing, ModuleName}); + _ -> + {{?MODULE, ModuleName}, State1} + end. + +%% @doc Manipulate the state of the system, that new state +%% +%% @param Provider the provider object +%% @param State the current state of the system +-spec do(Provider::t(), rcl_state:t()) -> + {ok, rcl_state:t()} | relcool:error(). +do({?MODULE, Mod}, State) -> + Mod:do(State). + +%%% @doc get the name of the module that implements the provider +%%% @param Provider the provider object +-spec impl(Provider::t()) -> module(). +impl({?MODULE, Mod}) -> + Mod. + +%% @doc format an error produced from a provider. +-spec format_error(Reason::term()) -> iolist(). +format_error({non_existing, ModuleName}) -> + io_lib:format("~p does not exist in the system", [ModuleName]). + +%% @doc format an error produced from a provider. +-spec format_error(t(), Reason::term()) -> iolist(). +format_error({?MODULE, Mod}, Error) -> + Mod:format_error(Error). + +%% @doc print the provider module name +%% +%% @param T - The provider +%% @return An iolist describing the provider +-spec format(t()) -> iolist(). +format({?MODULE, Mod}) -> + erlang:atom_to_list(Mod). diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl new file mode 100644 index 0000000..aca783d --- /dev/null +++ b/src/rlx_prv_assembler.erl @@ -0,0 +1,1003 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Given a complete built release this provider assembles that release +%%% into a release directory. +-module(rcl_prv_assembler). + +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +do(State) -> + {RelName, RelVsn} = rcl_state:default_configured_release(State), + Release = rcl_state:get_realized_release(State, RelName, RelVsn), + OutputDir = rcl_state:output_dir(State), + case create_output_dir(OutputDir) of + ok -> + case rcl_release:realized(Release) of + true -> + copy_app_directories_to_output(State, Release, OutputDir); + false -> + ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + end; + Error -> + Error + end. + +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error({unresolved_release, RelName, RelVsn}) -> + io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); +format_error({ec_file_error, AppDir, TargetDir, E}) -> + io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", + [AppDir, TargetDir, E]); +format_error({config_does_not_exist, Path}) -> + io_lib:format("The config file specified for this release (~s) does not exist!", + [Path]); +format_error({specified_erts_does_not_exist, ErtsVersion}) -> + io_lib:format("Specified version of erts (~s) does not exist", + [ErtsVersion]); +format_error({release_script_generation_error, RelFile}) -> + io_lib:format("Unknown internal release error generating the release file to ~s", + [RelFile]); +format_error({release_script_generation_warning, Module, Warnings}) -> + ["Warnings generating release \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({unable_to_create_output_dir, OutputDir}) -> + io_lib:format("Unable to create output directory (possible permissions issue): ~s", + [OutputDir]); +format_error({release_script_generation_error, Module, Errors}) -> + ["Errors generating release \n", + rcl_util:indent(1), Module:format_error(Errors)]; +format_error({relup_generation_error, CurrentName, UpFromName}) -> + io_lib:format("Unknown internal release error generating the relup from ~s to ~s", + [UpFromName, CurrentName]); +format_error({relup_generation_warning, Module, Warnings}) -> + ["Warnings generating relup \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({relup_script_generation_error, + {relupcript_generation_error, systools_relup, + {missing_sasl, _}}}) -> + "Unfortunately, due to requirements in systools, you need to have the sasl application " + "in both the current release and the release to upgrade from."; +format_error({relup_script_generation_error, Module, Errors}) -> + ["Errors generating relup \n", + rcl_util:indent(1), Module:format_error(Errors)]; +format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> + io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", + [AppDir, TargetDir, rcl_util:indent(1), + file:format_error(Reason)]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec create_output_dir(file:name()) -> + ok | {error, Reason::term()}. +create_output_dir(OutputDir) -> + case filelib:is_dir(OutputDir) of + false -> + case rcl_util:mkdir_p(OutputDir) of + ok -> + ok; + {error, _} -> + ?RCL_ERROR({unable_to_create_output_dir, OutputDir}) + end; + true -> + ok + end. + +copy_app_directories_to_output(State, Release, OutputDir) -> + LibDir = filename:join([OutputDir, "lib"]), + ok = ec_file:mkdir_p(LibDir), + Apps = rcl_release:application_details(Release), + Result = lists:filter(fun({error, _}) -> + true; + (_) -> + false + end, + lists:flatten(ec_plists:map(fun(App) -> + copy_app(LibDir, App) + end, Apps))), + case Result of + [E | _] -> + E; + [] -> + create_release_info(State, Release, OutputDir) + end. + +copy_app(LibDir, App) -> + AppName = erlang:atom_to_list(rcl_app_info:name(App)), + AppVsn = rcl_app_info:vsn_as_string(App), + AppDir = rcl_app_info:dir(App), + TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), + if + AppDir == TargetDir -> + %% No need to do anything here, discover found something already in + %% a release dir + ok; + true -> + copy_app(App, AppDir, TargetDir) + end. + +copy_app(App, AppDir, TargetDir) -> + remove_symlink_or_directory(TargetDir), + case rcl_app_info:link(App) of + true -> + link_directory(AppDir, TargetDir); + false -> + copy_directory(AppDir, TargetDir) + end. + +remove_symlink_or_directory(TargetDir) -> + case ec_file:is_symlink(TargetDir) of + true -> + ec_file:remove(TargetDir); + false -> + case filelib:is_dir(TargetDir) of + true -> + ok = ec_file:remove(TargetDir, [recursive]); + false -> + ok + end + end. + +link_directory(AppDir, TargetDir) -> + case file:make_symlink(AppDir, TargetDir) of + {error, Reason} -> + ?RCL_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); + ok -> + ok + end. + +copy_directory(AppDir, TargetDir) -> + ec_plists:map(fun(SubDir) -> + copy_dir(AppDir, TargetDir, SubDir) + end, ["ebin", + "include", + "priv", + "src", + "c_src", + "README", + "LICENSE"]). + +copy_dir(AppDir, TargetDir, SubDir) -> + SubSource = filename:join(AppDir, SubDir), + SubTarget = filename:join(TargetDir, SubDir), + case filelib:is_dir(SubSource) of + true -> + ok = rcl_util:mkdir_p(SubTarget), + case ec_file:copy(SubSource, SubTarget, [recursive]) of + {error, E} -> + ?RCL_ERROR({ec_file_error, AppDir, SubTarget, E}); + ok -> + ok + end; + false -> + ok + end. + +create_release_info(State0, Release0, OutputDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release0)), + ReleaseDir = release_output_dir(State0, Release0), + ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), + ok = ec_file:mkdir_p(ReleaseDir), + Release1 = rcl_release:relfile(Release0, ReleaseFile), + State1 = rcl_state:update_realized_release(State0, Release1), + case rcl_release:metadata(Release1) of + {ok, Meta} -> + ok = ec_file:write_term(ReleaseFile, Meta), + write_bin_file(State1, Release1, OutputDir, ReleaseDir); + E -> + E + end. + + +write_bin_file(State, Release, OutputDir, RelDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release)), + RelVsn = rcl_release:vsn(Release), + BinDir = filename:join([OutputDir, "bin"]), + ok = ec_file:mkdir_p(BinDir), + VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), + BareRel = filename:join(BinDir, RelName), + ErlOpts = rcl_state:get(State, erl_opts, ""), + StartFile = case rcl_state:get(State, extended_start_script, false) of + false -> + bin_file_contents(RelName, RelVsn, + rcl_release:erts(Release), + ErlOpts); + true -> + extended_bin_file_contents(RelName, RelVsn, rcl_release:erts(Release), ErlOpts) + end, + %% We generate the start script by default, unless the user + %% tells us not too + case rcl_state:get(State, generate_start_script, true) of + false -> + ok; + _ -> + ok = file:write_file(VsnRel, StartFile), + ok = file:change_mode(VsnRel, 8#777), + ok = file:write_file(BareRel, StartFile), + ok = file:change_mode(BareRel, 8#777) + end, + ok = file:write_file(filename:join(RelDir, "vm.args"), vm_args_file(RelName)), + copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir). + +%% @doc copy config/sys.config or generate one to releases/VSN/sys.config +-spec copy_or_generate_sys_config_file(rcl_state:t(), rcl_release:t(), + file:name(), file:name()) -> + {ok, rcl_state:t()} | relcool:error(). +copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir) -> + RelSysConfPath = filename:join([RelDir, "sys.config"]), + case rcl_state:sys_config(State) of + undefined -> + ok = generate_sys_config_file(RelSysConfPath), + include_erts(State, Release, OutputDir, RelDir); + ConfigPath -> + case filelib:is_regular(ConfigPath) of + false -> + ?RCL_ERROR({config_does_not_exist, ConfigPath}); + true -> + ok = ec_file:copy(ConfigPath, RelSysConfPath), + include_erts(State, Release, OutputDir, RelDir) + end + end. + +%% @doc write a generic sys.config to the path RelSysConfPath +-spec generate_sys_config_file(string()) -> ok. +generate_sys_config_file(RelSysConfPath) -> + {ok, Fd} = file:open(RelSysConfPath, [write]), + io:format(Fd, + "%% Thanks to Ulf Wiger at Ericcson for these comments:~n" + "%%~n" + "%% This file is identified via the erl command line option -config File.~n" + "%% Note that File should have no extension, e.g.~n" + "%% erl -config .../sys (if this file is called sys.config)~n" + "%%~n" + "%% In this file, you can redefine application environment variables.~n" + "%% This way, you don't have to modify the .app files of e.g. OTP applications.~n" + "[].~n", []), + file:close(Fd). + +%% @doc Optionally add erts directory to release, if defined. +-spec include_erts(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> {ok, rcl_state:t()} | relcool:error(). +include_erts(State, Release, OutputDir, RelDir) -> + case rcl_state:get(State, include_erts, true) of + true -> + Prefix = code:root_dir(), + ErtsVersion = rcl_release:erts(Release), + ErtsDir = filename:join([Prefix, "erts-" ++ ErtsVersion]), + LocalErts = filename:join([OutputDir, "erts-" ++ ErtsVersion]), + case filelib:is_dir(ErtsDir) of + false -> + ?RCL_ERROR({specified_erts_does_not_exist, ErtsVersion}); + true -> + ok = ec_file:mkdir_p(LocalErts), + ok = ec_file:copy(ErtsDir, LocalErts, [recursive]), + ok = ec_file:remove(filename:join([LocalErts, "bin", "erl"])), + ok = file:write_file(filename:join([LocalErts, "bin", "erl"]), erl_script(ErtsVersion)), + case rcl_state:get(State, extended_start_script, false) of + true -> + ok = ec_file:copy(filename:join([Prefix, "bin", "start_clean.boot"]), + filename:join([OutputDir, "bin", "start_clean.boot"])), + NodeToolFile = nodetool_contents(), + NodeTool = filename:join([LocalErts, "bin", "nodetool"]), + ok = file:write_file(NodeTool, NodeToolFile), + ok = file:change_mode(NodeTool, 8#755); + false -> + ok + end, + make_boot_script(State, Release, OutputDir, RelDir) + end; + _ -> + make_boot_script(State, Release, OutputDir, RelDir) + end. + + +-spec make_boot_script(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> + {ok, rcl_state:t()} | relcool:error(). +make_boot_script(State, Release, OutputDir, RelDir) -> + Options = [{path, [RelDir | get_code_paths(Release, OutputDir)]}, + {outdir, RelDir}, + no_module_tests, silent], + Name = erlang:atom_to_list(rcl_release:name(Release)), + ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), + case make_script(Options, + fun(CorrectedOptions) -> + systools:make_script(Name, CorrectedOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "release successfully created!"), + make_relup(State, Release); + error -> + ?RCL_ERROR({release_script_generation_error, ReleaseFile}); + {ok, _, []} -> + rcl_log:error(rcl_state:log(State), + "release successfully created!"), + make_relup(State, Release); + {ok,Module,Warnings} -> + ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); + {error,Module,Error} -> + ?RCL_ERROR({release_script_generation_error, Module, Error}) + end. + +-spec make_script([term()], + fun(([term()]) -> Res)) -> Res. +make_script(Options, RunFun) -> + %% Erts 5.9 introduced a non backwards compatible option to + %% erlang this takes that into account + Erts = erlang:system_info(version), + case ec_semver:gte(Erts, "5.9") of + true -> + RunFun([no_warn_sasl | Options]); + _ -> + RunFun(Options) + end. + +make_relup(State, Release) -> + case rcl_state:action(State) of + relup -> + UpFrom = + case rcl_state:upfrom(State) of + undefined -> + get_last_release(State, Release); + Vsn -> + get_up_release(State, Release, Vsn) + end, + case UpFrom of + undefined -> + ?RCL_ERROR(no_upfrom_release_found); + _ -> + make_upfrom_script(State, Release, UpFrom) + end; + _ -> + {ok, State} + end. + +make_upfrom_script(State, Release, UpFrom) -> + OutputDir = rcl_state:output_dir(State), + Options = [{outdir, OutputDir}, + {path, get_code_paths(Release, OutputDir) ++ + get_code_paths(UpFrom, OutputDir)}, + silent], + CurrentRel = strip_rel(rcl_release:relfile(Release)), + UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), + rcl_log:debug(rcl_state:log(State), + "systools:make_relup(~p, ~p, ~p, ~p)", + [CurrentRel, UpFromRel, UpFromRel, Options]), + case make_script(Options, + fun(CorrectOptions) -> + systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "relup from ~s to ~s successfully created!", + [UpFromRel, CurrentRel]), + {ok, State}; + error -> + ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); + {ok, RelUp, _, []} -> + rcl_log:error(rcl_state:log(State), + "relup successfully created!"), + write_relup_file(State, Release, RelUp), + {ok, State}; + {ok,_, Module,Warnings} -> + ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); + {error,Module,Errors} -> + ?RCL_ERROR({relupcript_generation_error, Module, Errors}) + end. + +write_relup_file(State, Release, Relup) -> + OutDir = release_output_dir(State, Release), + RelName = rcl_util:to_string(rcl_release:name(Release)), + RelupFile = filename:join(OutDir, RelName ++ ".relup"), + ok = ec_file:write_term(RelupFile, Relup). + +strip_rel(Name) -> + rcl_util:to_string(filename:join(filename:dirname(Name), + filename:basename(Name, ".rel"))). + + +get_up_release(State, Release, Vsn) -> + Name = rcl_release:name(Release), + try + ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) + catch + throw:notfound -> + undefined + end. + +get_last_release(State, Release) -> + Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], + Releases1 = lists:sort(fun(R1, R2) -> + ec_semver:lte(rcl_release:vsn(R1), + rcl_release:vsn(R2)) + end, Releases0), + Res = lists:foldl(fun(_Rel, R = {found, _}) -> + R; + (Rel, Prev) -> + case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of + true -> + {found, Prev}; + false -> + Rel + end + end, undefined, Releases1), + case Res of + {found, R} -> + R; + Else -> + Else + end. + +-spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). +release_output_dir(State, Release) -> + OutputDir = rcl_state:output_dir(State), + filename:join([OutputDir, + "releases", + rcl_release:canonical_name(Release)]). + +%% @doc Generates the correct set of code paths for the system. +-spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. +get_code_paths(Release, OutDir) -> + LibDir = filename:join(OutDir, "lib"), + [filename:join([LibDir, + erlang:atom_to_list(rcl_app_info:name(App)) ++ "-" ++ + rcl_app_info:vsn_as_string(App), "ebin"]) || + App <- rcl_release:application_details(Release)]. + +erl_script(ErtsVsn) -> + [<<"#!/bin/sh +set -e + +SCRIPT_DIR=`dirname $0` +ROOTDIR=`cd $SCRIPT_DIR/../../ && pwd` +BINDIR=$ROOTDIR/erts-">>, ErtsVsn, <<"/bin +EMU=beam +PROGNAME=`echo $0 | sed 's/.*\\///'` +export EMU +export ROOTDIR +export BINDIR +export PROGNAME +exec \"$BINDIR/erlexec\" ${1+\"$@\"} +">>]. + +bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> + [<<"#!/bin/sh + +set -e + +SCRIPT_DIR=`dirname $0` +RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` +REL_NAME=">>, RelName, <<" +REL_VSN=">>, RelVsn, <<" +ERTS_VSN=">>, ErtsVsn, <<" +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +ERL_OPTS=">>, ErlOpts, <<" + +find_erts_dir() { + local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN + if [ -d \"$erts_dir\" ]; then + ERTS_DIR=$erts_dir; + ROOTDIR=$RELEASE_ROOT_DIR + else + local erl=`which erl` + local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` + ERTS_DIR=$erl_root/erts-$ERTS_VSN + ROOTDIR=$erl_root + fi + +} + +find_sys_config() { + local possible_sys=$REL_DIR/sys.config + if [ -f \"$possible_sys\" ]; then + SYS_CONFIG=\"-config $possible_sys\" + fi +} + +find_erts_dir +find_sys_config +export ROOTDIR=$RELEASE_ROOT_DIR +export BINDIR=$ERTS_DIR/bin +export EMU=beam +export PROGNAME=erl +export LD_LIBRARY_PATH=$ERTS_DIR/lib + +cd $ROOTDIR + +$BINDIR/erlexec $ERL_OPTS $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@">>]. + +extended_bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> + [<<"#!/bin/sh + +set -e + +SCRIPT_DIR=`dirname $0` +RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` +REL_NAME=">>, RelName, <<" +REL_VSN=">>, RelVsn, <<" +ERTS_VSN=">>, ErtsVsn, <<" +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +ERL_OPTS=">>, ErlOpts, <<" +PIPE_DIR=/tmp/$REL_DIR/ + +find_erts_dir() { + local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN + if [ -d \"$erts_dir\" ]; then + ERTS_DIR=$erts_dir; + ROOTDIR=$RELEASE_ROOT_DIR + else + local erl=`which erl` + local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` + ERTS_DIR=$erl_root/erts-$ERTS_VSN + ROOTDIR=$erl_root + fi + +} + +find_sys_config() { + local possible_sys=$REL_DIR/sys.config + if [ -f \"$possible_sys\" ]; then + SYS_CONFIG=\"-config $possible_sys\" + fi +} + +# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or else etc/vm.args +if [ -e \"$CALLER_DIR/vm.args\" ]; then + VMARGS_PATH=$CALLER_DIR/vm.args + USE_DIR=$CALLER_DIR +else + USE_DIR=$REL_DIR + if [ -e \"$REL_DIR/vm.args\" ]; then + VMARGS_PATH=\"$REL_DIR/vm.args\" + else + VMARGS_PATH=\"$REL_DIR/vm.args\" + fi +fi + +RUNNER_LOG_DIR=$USE_DIR/log +# Make sure log directory exists +mkdir -p $RUNNER_LOG_DIR + +# Use releases/VSN/sys.config if it exists otherwise use etc/app.config +if [ -e \"$USE_DIR/sys.config\" ]; then + CONFIG_PATH=\"$USE_DIR/sys.config\" +else + if [ -e \"$REL_DIR/sys.config\" ]; then + CONFIG_PATH=\"$REL_DIR/sys.config\" + else + CONFIG_PATH=\"$REL_DIR/app.config\" + fi +fi + +# Extract the target node name from node.args +NAME_ARG=`egrep '^-s?name' $VMARGS_PATH` +if [ -z \"$NAME_ARG\" ]; then + echo \"vm.args needs to have either -name or -sname parameter.\" + exit 1 +fi + +# Extract the name type and name from the NAME_ARG for REMSH +REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` +REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` + +# Note the `date +%s`, used to allow multiple remsh to the same node transparently +REMSH_NAME_ARG=\"$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`\" +REMSH_REMSH_ARG=\"-remsh $REMSH_NAME\" + +# Extract the target cookie +COOKIE_ARG=`grep '^-setcookie' $VMARGS_PATH` +if [ -z \"$COOKIE_ARG\" ]; then + echo \"vm.args needs to have a -setcookie parameter.\" + exit 1 +fi + +# Setup remote shell command to control node +REMSH=\"$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG\" + +find_erts_dir +find_sys_config +export ROOTDIR=$RELEASE_ROOT_DIR +export BINDIR=$ERTS_DIR/bin +export EMU=beam +export PROGNAME=erl +export LD_LIBRARY_PATH=$ERTS_DIR/lib + +cd $ROOTDIR + +# Setup command to control the node +NODETOOL=\"$ERTS_DIR/bin/escript $ERTS_DIR/bin/nodetool $NAME_ARG $COOKIE_ARG\" + +# Check the first argument for instructions +case \"$1\" in + start|start_boot) + + # Make sure there is not already a node running + #RES=`$NODETOOL ping` + #if [ \"$RES\" = \"pong\" ]; then + # echo \"Node is already running!\" + # exit 1 + #fi + case \"$1\" in + start) + shift + START_OPTION=\"console\" + HEART_OPTION=\"start\" + ;; + start_boot) + shift + START_OPTION=\"console_boot\" + HEART_OPTION=\"start_boot\" + ;; + esac + RUN_PARAM=$(printf \"'%s' \" \"$@\") + HEART_COMMAND=\"$SCRIPT_DIR/bin/$REL_NAME $HEART_OPTION $RUN_PARAM\" + export HEART_COMMAND + mkdir -p $PIPE_DIR + $ERTS_DIR/bin/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR \"exec $RELEASE_ROOT_DIR/bin/$REL_NAME $START_OPTION $RUN_PARAM\" 2>&1 + ;; + + stop) + # Wait for the node to completely stop... + case `uname -s` in + Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) + # PID COMMAND + PID=`ps ax -o pid= -o command=| + grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` + ;; + SunOS) + # PID COMMAND + PID=`ps -ef -o pid= -o args=| + grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` + ;; + CYGWIN*) + # UID PID PPID TTY STIME COMMAND + PID=`ps -efW|grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $2}'` + ;; + esac + $NODETOOL stop + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + while `kill -0 $PID 2>/dev/null`; + do + sleep 1 + done + ;; + + restart) + ## Restart the VM without exiting the process + $NODETOOL restart + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + ;; + + reboot) + ## Restart the VM completely (uses heart to restart it) + $NODETOOL reboot + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + ;; + + ping) + ## See if the VM is alive + $NODETOOL ping + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + ;; + + attach) + # Make sure a node IS running + RES=`$NODETOOL ping` + ES=$? + if [ \"$ES\" -ne 0 ]; then + echo \"Node is not running!\" + exit $ES + fi + + shift + exec $ERTS_DIR/bin/to_erl $PIPE_DIR + ;; + + remote_console) + # Make sure a node IS running + RES=`$NODETOOL ping` + ES=$? + if [ \"$ES\" -ne 0 ]; then + echo \"Node is not running!\" + exit $ES + fi + + shift + exec $REMSH + ;; + + upgrade) + if [ -z \"$2\" ]; then + echo \"Missing upgrade package argument\" + echo \"Usage: $REL_NAME upgrade {package base name}\" + echo \"NOTE {package base name} MUST NOT include the .tar.gz suffix\" + exit 1 + fi + + # Make sure a node IS running + RES=`$NODETOOL ping` + ES=$? + if [ \"$ES\" -ne 0 ]; then + echo \"Node is not running!\" + exit $ES + fi + + node_name=`echo $NAME_ARG | awk '{print $2}'` + erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` + + $ERTS_DIR/bin/escript $SCRIPT_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 + ;; + + console|console_clean|console_boot) + # .boot file typically just $REL_NAME (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful. + # For e.g. 'setup', one may even want to name another boot script. + case \"$1\" in + console) BOOTFILE=$REL_NAME ;; + console_clean) BOOTFILE=start_clean ;; + console_boot) + shift + BOOTFILE=\"$1\" + shift + ;; + esac + # Setup beam-required vars + ROOTDIR=$RELEASE_ROOT_DIR + BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\\///'` + CMD=\"$BINDIR/erlexec -boot $REL_DIR/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH\" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo \"Exec: $CMD\" -- ${1+\"$@\"} + echo \"Root: $ROOTDIR\" + + # Log the startup + logger -t \"$REL_NAME[$$]\" \"Starting up\" + + # Start the VM + exec $CMD -- ${1+\"$@\"} + ;; + + foreground) + # start up the release in the foreground for use by runit + # or other supervision services + + BOOTFILE=$REL_NAME + FOREGROUNDOPTIONS=\"-noinput +Bd\" + + # Setup beam-required vars + ROOTDIR=$RELEASE_ROOT_DIR + BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*///'` + CMD=\"$BINDIR/erlexec $FOREGROUNDOPTIONS -boot $REL_DIR/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH\" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo \"Exec: $CMD\" -- ${1+\"$@\"} + echo \"Root: $ROOTDIR\" + + # Start the VM + exec $CMD -- ${1+\"$@\"} + ;; + *) + echo \"Usage: $REL_NAME {start|start_boot |foreground|stop|restart|reboot|ping|console|console_clean|console_boot |attach|remote_console|upgrade}\" + exit 1 + ;; +esac + +exit 0">>]. + +nodetool_contents() -> + [<<"%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% nodetool: Helper Script for interacting with live nodes +%% +%% ------------------------------------------------------------------- + +main(Args) -> + ok = start_epmd(), + %% Extract the args + {RestArgs, TargetNode} = process_args(Args, [], undefined), + + %% See if the node is currently running -- if it's not, we'll bail + case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + {true, pong} -> + ok; + {_, pang} -> + io:format(\"Node ~p not responding to pings.\n\", [TargetNode]), + halt(1) + end, + + case RestArgs of + [\"ping\"] -> + %% If we got this far, the node already responsed to a ping, so just dump + %% a \"pong\" + io:format(\"pong\n\"); + [\"stop\"] -> + io:format(\"~p\n\", [rpc:call(TargetNode, init, stop, [], 60000)]); + [\"restart\"] -> + io:format(\"~p\n\", [rpc:call(TargetNode, init, restart, [], 60000)]); + [\"reboot\"] -> + io:format(\"~p\n\", [rpc:call(TargetNode, init, reboot, [], 60000)]); + [\"rpc\", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + [RpcArgs], 60000) of + ok -> + ok; + {badrpc, Reason} -> + io:format(\"RPC to ~p failed: ~p\n\", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; + [\"rpcterms\", Module, Function, ArgsAsString] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + consult(ArgsAsString), 60000) of + {badrpc, Reason} -> + io:format(\"RPC to ~p failed: ~p\n\", [TargetNode, Reason]), + halt(1); + Other -> + io:format(\"~p\n\", [Other]) + end; + Other -> + io:format(\"Other: ~p\n\", [Other]), + io:format(\"Usage: nodetool {ping|stop|restart|reboot}\n\") + end, + net_kernel:stop(). + +process_args([], Acc, TargetNode) -> + {lists:reverse(Acc), TargetNode}; +process_args([\"-setcookie\", Cookie | Rest], Acc, TargetNode) -> + erlang:set_cookie(node(), list_to_atom(Cookie)), + process_args(Rest, Acc, TargetNode); +process_args([\"-name\", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, \"_maint_\"), + {ok, _} = net_kernel:start([ThisNode, longnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args([\"-sname\", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, \"_maint_\"), + {ok, _} = net_kernel:start([ThisNode, shortnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args([Arg | Rest], Acc, Opts) -> + process_args(Rest, [Arg | Acc], Opts). + + +start_epmd() -> + [] = os:cmd(epmd_path() ++ \" -daemon\"), + ok. + +epmd_path() -> + ErtsBinDir = filename:dirname(escript:script_name()), + Name = \"epmd\", + case os:find_executable(Name, ErtsBinDir) of + false -> + case os:find_executable(Name) of + false -> + io:format(\"Could not find epmd.~n\"), + halt(1); + GlobalEpmd -> + GlobalEpmd + end; + Epmd -> + Epmd + end. + + +nodename(Name) -> + case string:tokens(Name, \"@\") of + [_Node, _Host] -> + list_to_atom(Name); + [Node] -> + [_, Host] = string:tokens(atom_to_list(node()), \"@\"), + list_to_atom(lists:concat([Node, \"@\", Host])) + end. + +append_node_suffix(Name, Suffix) -> + case string:tokens(Name, \"@\") of + [Node, Host] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid(), \"@\", Host])); + [Node] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid()])) + end. + + +%% +%% Given a string or binary, parse it into a list of terms, ala file:consult/0 +%% +consult(Str) when is_list(Str) -> + consult([], Str, []); +consult(Bin) when is_binary(Bin)-> + consult([], binary_to_list(Bin), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + {ok, Term} = erl_parse:parse_term(Tokens), + consult([], Remaining, [Term | Acc]); + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end.">>]. + +vm_args_file(RelName) -> + [<<"## Name of the node +-name ">>, RelName, <<"@127.0.0.1 + +## Cookie for distributed erlang +-setcookie ">>, RelName, <<" + +## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive +## (Disabled by default..use with caution!) +##-heart + +## Enable kernel poll and a few async threads +##+K true +##+A 5 + +## Increase number of concurrent ports/sockets +##-env ERL_MAX_PORTS 4096 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10">>]. diff --git a/src/rlx_prv_config.erl b/src/rlx_prv_config.erl new file mode 100644 index 0000000..2bc9851 --- /dev/null +++ b/src/rlx_prv_config.erl @@ -0,0 +1,186 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright 2011 Erlware, LLC. +%%% @doc +%%% A module that provides config parsing and support to the system +%%% @end +%%%------------------------------------------------------------------- +-module(rcl_prv_config). + +-behaviour(rcl_provider). + +%% API +-export([init/1, + do/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Required by the system, but not used in this provider +-spec init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +init(State) -> + {ok, State}. + +%% @doc parse all the configs currently specified in the state, +%% populating the state as a result. +-spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error(). +do(State) -> + case rcl_state:config_file(State) of + [] -> + search_for_dominating_config(State); + undefined -> + search_for_dominating_config(State); + ConfigFile when erlang:is_list(ConfigFile) -> + load_config(ConfigFile, State) + end. + +-spec format_error(Reason::term()) -> iolist(). +format_error({consult, ConfigFile, Reason}) -> + io_lib:format("Unable to read file ~s: ~s", [ConfigFile, + file:format_error(Reason)]); +format_error({invalid_term, Term}) -> + io_lib:format("Invalid term in config file: ~p", [Term]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +search_for_dominating_config({ok, Cwd}) -> + ConfigFile = filename:join(Cwd, "relcool.config"), + case ec_file:exists(ConfigFile) of + true -> + {ok, ConfigFile}; + false -> + search_for_dominating_config(parent_dir(Cwd)) + end; +search_for_dominating_config({error, _}) -> + no_config; +search_for_dominating_config(State0) -> + {ok, Cwd} = file:get_cwd(), + case search_for_dominating_config({ok, Cwd}) of + {ok, Config} -> + %% we need to set the root dir on state as well + {ok, RootDir} = parent_dir(Config), + State1 = rcl_state:root_dir(State0, RootDir), + load_config(Config, rcl_state:config_file(State1, Config)); + no_config -> + {ok, State0} + end. + +%% @doc Given a directory returns the name of the parent directory. +-spec parent_dir(Filename::string()) -> + {ok, DirName::string()} | {error, no_parent_dir}. +parent_dir(Filename) -> + parent_dir(filename:split(Filename), []). + +%% @doc Given list of directories, splits the list and returns all dirs but the +%% last as a path. +-spec parent_dir([string()], [string()]) -> + {ok, DirName::string()} | {error, no_parent_dir}. +parent_dir([_H], []) -> + {error, no_parent_dir}; +parent_dir([], []) -> + {error, no_parent_dir}; +parent_dir([_H], Acc) -> + {ok, filename:join(lists:reverse(Acc))}; +parent_dir([H | T], Acc) -> + parent_dir(T, [H | Acc]). + + +-spec load_config(file:filename(), rcl_state:t()) -> + {ok, rcl_state:t()} | relcool:error(). +load_config(ConfigFile, State) -> + {ok, CurrentCwd} = file:get_cwd(), + ok = file:set_cwd(filename:dirname(ConfigFile)), + Result = case file:consult(ConfigFile) of + {error, Reason} -> + ?RCL_ERROR({consult, ConfigFile, Reason}); + {ok, Terms} -> + lists:foldl(fun load_terms/2, {ok, State}, Terms) + end, + ok = file:set_cwd(CurrentCwd), + Result. + +-spec load_terms(term(), {ok, rcl_state:t()} | relcool:error()) -> + {ok, rcl_state:t()} | relcool:error(). +load_terms({default_release, RelName, RelVsn}, {ok, State}) -> + {ok, rcl_state:default_configured_release(State, RelName, RelVsn)}; +load_terms({paths, Paths}, {ok, State}) -> + code:add_pathsa([filename:absname(Path) || Path <- Paths]), + {ok, State}; +load_terms({providers, Providers0}, {ok, State0}) -> + Providers1 = gen_providers(Providers0, State0), + case Providers1 of + {_, E={error, _}} -> + E; + {Providers3, {ok, State3}} -> + {ok, rcl_state:providers(State3, Providers3)} + end; +load_terms({add_providers, Providers0}, {ok, State0}) -> + Providers1 = gen_providers(Providers0, State0), + case Providers1 of + {_, E={error, _}} -> + E; + {Providers3, {ok, State1}} -> + ExistingProviders = rcl_state:providers(State1), + {ok, rcl_state:providers(State1, ExistingProviders ++ Providers3)} + end; +load_terms({skip_apps, SkipApps0}, {ok, State0}) -> + {ok, rcl_state:skip_apps(State0, SkipApps0)}; +load_terms({overrides, Overrides0}, {ok, State0}) -> + {ok, rcl_state:overrides(State0, Overrides0)}; +load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) -> + Release0 = rcl_release:new(RelName, Vsn), + case rcl_release:goals(Release0, Applications) of + E={error, _} -> + E; + {ok, Release1} -> + {ok, rcl_state:add_configured_release(State0, Release1)} + end; +load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, + Applications}, {ok, State}) -> + Release0 = rcl_release:erts(rcl_release:new(RelName, Vsn), ErtsVsn), + case rcl_release:goals(Release0, Applications) of + E={error, _} -> + E; + {ok, Release1} -> + {ok, rcl_state:add_configured_release(State, Release1)} + end; +load_terms({sys_config, SysConfig}, {ok, State}) -> + {ok, rcl_state:sys_config(State, filename:absname(SysConfig))}; +load_terms({Name, Value}, {ok, State}) + when erlang:is_atom(Name) -> + {ok, rcl_state:put(State, Name, Value)}; +load_terms(_, Error={error, _}) -> + Error; +load_terms(InvalidTerm, _) -> + ?RCL_ERROR({invalid_term, InvalidTerm}). + +-spec gen_providers([module()], rcl_state:t()) -> + {[rcl_provider:t()], {ok, rcl_state:t()} | relcool:error()}. +gen_providers(Providers, State) -> + lists:foldl(fun(ProviderName, {Providers1, {ok, State1}}) -> + {Provider, State2} = rcl_provider:new(ProviderName, State1), + {[Provider | Providers1], State2}; + (_, E={_, {error, _}}) -> + E + end, {[], {ok, State}}, Providers). diff --git a/src/rlx_prv_discover.erl b/src/rlx_prv_discover.erl new file mode 100644 index 0000000..56085ac --- /dev/null +++ b/src/rlx_prv_discover.erl @@ -0,0 +1,133 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provider uses the lib_dir setting of the state. It searches the +%%% Lib Dirs looking for all OTP Applications that are available. When it finds +%%% those OTP Applications it loads the information about them and adds them to +%%% the state of available apps. This implements the rcl_provider behaviour. +-module(rcl_prv_discover). +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +do(State0) -> + LibDirs = get_lib_dirs(State0), + case rcl_app_discovery:do(State0, LibDirs) of + {ok, AppMeta} -> + case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of + {ok, Releases} -> + State1 = rcl_state:available_apps(State0, AppMeta), + {ok, rcl_state:realized_releases(State1, lists:foldl(fun add/2, + ec_dictionary:new(ec_dict), + Releases))}; + Error -> + Error + end; + Error -> + Error + end. + +%% @doc this is here to comply with the signature. However, we do not actually +%% produce any errors and so simply return an empty string. +-spec format_error(any()) -> iolist(). +format_error(_) -> + "". + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +%% @doc only add the release if its not documented in the system +add(Rel, Dict) -> + RelName = rcl_release:name(Rel), + RelVsn = rcl_release:vsn(Rel), + ec_dictionary:add({RelName, RelVsn}, Rel, Dict). + +get_lib_dirs(State) -> + LibDirs0 = rcl_state:lib_dirs(State), + case rcl_state:get(State, disable_default_libs, false) of + true -> + LibDirs0; + false -> + lists:flatten([LibDirs0, + add_common_project_dirs(State), + add_system_lib_dir(State), + add_release_output_dir(State)]) + end. + +-spec add_common_project_dirs(rcl_state:t()) -> [file:name()]. +add_common_project_dirs(State) -> + %% Check to see if there is a rebar.config. If so then look for a deps + %% dir. If both are there then we add that to the lib dirs. + case rcl_state:get(State, disable_project_subdirs, false) of + true -> + []; + false -> + Root = rcl_state:root_dir(State), + Apps = filename:join(Root, "apps"), + Lib = filename:join(Root, "lib"), + Deps = filename:join(Root, "deps"), + Ebin = filename:join(Root, "ebin"), + lists:foldl(fun(Dir, LibDirs) -> + case ec_file:exists(Dir) of + true -> + [erlang:iolist_to_binary(Dir) | LibDirs]; + false -> + LibDirs + end + end, [], [Deps, Lib, Apps, Ebin]) + end. + +-spec add_system_lib_dir(rcl_state:t()) -> [file:name()]. +add_system_lib_dir(State) -> + ExcludeSystem = rcl_state:get(State, discover_exclude_system, false), + case ExcludeSystem of + true -> + []; + false -> + erlang:iolist_to_binary(code:lib_dir()) + end. + +add_release_output_dir(State) -> + case rcl_state:get(State, disable_discover_release_output, false) of + true -> + []; + false -> + Output = erlang:iolist_to_binary(rcl_state:output_dir(State)), + case ec_file:exists(erlang:binary_to_list(Output)) of + true -> + Output; + false -> + [] + end + end. diff --git a/src/rlx_prv_overlay.erl b/src/rlx_prv_overlay.erl new file mode 100644 index 0000000..35e37bf --- /dev/null +++ b/src/rlx_prv_overlay.erl @@ -0,0 +1,418 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Given a complete built release this provider assembles that release +%%% into a release directory. +-module(rcl_prv_overlay). + +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format_error/1]). + +-define(DIRECTORY_RE, ".*(\/|\\\\)$"). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +do(State) -> + {RelName, RelVsn} = rcl_state:default_configured_release(State), + Release = rcl_state:get_realized_release(State, RelName, RelVsn), + case rcl_release:realized(Release) of + true -> + generate_overlay_vars(State, Release); + false -> + ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + end. + +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error({unresolved_release, RelName, RelVsn}) -> + io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); +format_error({ec_file_error, AppDir, TargetDir, E}) -> + io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", + [AppDir, TargetDir, E]); +format_error({unable_to_read_varsfile, FileName, Reason}) -> + io_lib:format("Unable to read vars file (~s) for overlay due to: ~p", + [FileName, Reason]); +format_error({overlay_failed, Errors}) -> + [[format_error(rcl_util:error_reason(Error)), "\n"] || Error <- Errors]; +format_error({dir_render_failed, Dir, Error}) -> + io_lib:format("rendering mkdir path failed ~s with ~p", + [Dir, Error]); +format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> + io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", + [AppDir, TargetDir, rcl_util:indent(1), + file:format_error(Reason)]); +format_error({copy_failed, FromFile, ToFile, Err}) -> + io_lib:format("Unable to copy from ~s to ~s because of ~p", + [FromFile, ToFile, Err]); +format_error({unable_to_write, ToFile, Reason}) -> + io_lib:format("Unable to write to ~s because ~p", + [ToFile, Reason]); +format_error({unable_to_enclosing_dir, ToFile, Reason}) -> + io_lib:format("Unable to create enclosing directory for ~s because ~p", + [ToFile, Reason]); +format_error({unable_to_render_template, FromFile, Reason}) -> + io_lib:format("Unable to render template ~s because ~p", + [FromFile, Reason]); +format_error({unable_to_compile_template, FromFile, Reason}) -> + io_lib:format("Unable to compile template ~s because ~p", + [FromFile, Reason]); +format_error({unable_to_make_dir, Absolute, Error}) -> + io_lib:format("Unable to make directory ~s because ~p", + [Absolute, Error]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec generate_overlay_vars(rcl_state:t(), rcl_release:t()) -> + {ok, rcl_state:t()} | relcool:error(). +generate_overlay_vars(State, Release) -> + StateVars = generate_state_vars(State), + ReleaseVars = generate_release_vars(Release), + get_overlay_vars_from_file(State, StateVars ++ ReleaseVars). + +-spec get_overlay_vars_from_file(rcl_state:t(), proplists:proplist()) -> + {ok, rcl_state:t()} | relcool:error(). +get_overlay_vars_from_file(State, OverlayVars) -> + case rcl_state:get(State, overlay_vars, undefined) of + undefined -> + do_overlay(State, OverlayVars); + FileName -> + read_overlay_vars(State, OverlayVars, FileName) + end. + +-spec read_overlay_vars(rcl_state:t(), proplists:proplist(), file:name()) -> + {ok, rcl_state:t()} | relcool:error(). +read_overlay_vars(State, OverlayVars, FileName) -> + RelativeRoot = get_relative_root(State), + RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)), + case file:consult(RelativePath) of + {ok, Terms} -> + case render_overlay_vars(OverlayVars, Terms, []) of + {ok, NewTerms} -> + do_overlay(State, NewTerms ++ OverlayVars); + Error -> + Error + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_read_varsfile, FileName, Reason}) + end. + +-spec render_overlay_vars(proplists:proplist(), proplists:proplist(), + proplists:proplist()) -> + {ok, proplists:proplist()} | relcool:error(). +render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) + when erlang:is_list(Value) -> + case io_lib:printable_list(Value) of + true -> + case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of + {ok, Data} -> + %% Adding to the end sucks, but ordering needs to be retained + render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, Data}]); + Error -> + Error + end; + false -> + case render_overlay_vars(Acc ++ OverlayVars, Value, []) of + {ok, NewValue} -> + render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, NewValue}]); + Error -> + Error + end + end; +render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) + when erlang:is_binary(Value) -> + case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of + {ok, Data} -> + render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, erlang:iolist_to_binary(Data)}]); + Error -> + Error + end; +render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) -> + render_overlay_vars(OverlayVars, Rest, Acc ++ [KeyValue]); +render_overlay_vars(_OverlayVars, [], Acc) -> + {ok, Acc}. + +-spec generate_release_vars(rcl_release:t()) -> proplists:proplist(). +generate_release_vars(Release) -> + [{erts_vsn, rcl_release:erts(Release)}, + {release_erts_version, rcl_release:erts(Release)}, + {release_name, rcl_release:name(Release)}, + {rel_vsn, rcl_release:vsn(Release)}, + {release_version, rcl_release:vsn(Release)}, + {release_applications, lists:map(fun(App) -> + rcl_app_info:name(App) + end, rcl_release:application_details(Release))}, + {release, [generate_app_vars(App)|| App <- rcl_release:application_details(Release)]}, + {release_goals, [if + erlang:is_list(Constraint) -> + Constraint; + true -> + rcl_depsolver:format_constraint(Constraint) + end || Constraint <- rcl_release:goals(Release)]}]. + +-spec generate_app_vars(rcl_app_info:t()) -> AppInfo::tuple(). +generate_app_vars(App) -> + {rcl_app_info:name(App), + [{version, rcl_app_info:vsn_as_string(App)}, + {dir, rcl_app_info:dir(App)}, + {active_dependencies, rcl_app_info:active_deps(App)}, + {library_dependencies, rcl_app_info:library_deps(App)}, + {link, rcl_app_info:link(App)}]}. + +-spec generate_state_vars(rcl_state:t()) -> proplists:proplist(). +generate_state_vars(State) -> + [{log, rcl_log:format(rcl_state:log(State))}, + {output_dir, rcl_state:output_dir(State)}, + {target_dir, rcl_state:output_dir(State)}, + {overridden, [AppName || {AppName, _} <- rcl_state:overrides(State)]}, + {overrides, rcl_state:overrides(State)}, + {goals, [rcl_depsolver:format_constraint(Constraint) || + Constraint <- rcl_state:goals(State)]}, + {lib_dirs, rcl_state:lib_dirs(State)}, + {config_file, rcl_state:config_file(State)}, + {providers, rcl_state:providers(State)}, + {sys_config, rcl_state:sys_config(State)}, + {root_dir, rcl_state:root_dir(State)}, + {default_release_name, case rcl_state:default_configured_release(State) of + {Name0, _} -> + Name0 + end}, + {default_release_version, case rcl_state:default_configured_release(State) of + {_, Vsn0} -> + Vsn0 + end}, + {default_release, case rcl_state:default_configured_release(State) of + {Name1, undefined} -> + erlang:atom_to_list(Name1); + {Name1, Vsn1} -> + erlang:atom_to_list(Name1) ++ "-" ++ Vsn1 + end}]. + +-spec do_overlay(rcl_state:t(), proplists:proplist()) -> + {ok, rcl_state:t()} | relcool:error(). +do_overlay(State, OverlayVars) -> + case rcl_state:get(State, overlay, undefined) of + undefined -> + {ok, State}; + Overlays -> + handle_errors(State, + lists:map(fun(Overlay) -> + do_individual_overlay(State, OverlayVars, + Overlay) + end, Overlays)) + end. + +-spec handle_errors(rcl_state:t(), [ok | relcool:error()]) -> + {ok, rcl_state:t()} | relcool:error(). +handle_errors(State, Result) -> + case [Error || Error <- Result, + rcl_util:is_error(Error)] of + Errors = [_|_] -> + ?RCL_ERROR({overlay_failed, Errors}); + [] -> + {ok, State} + end. + +-spec do_individual_overlay(rcl_state:t(), proplists:proplist(), + OverlayDirective::term()) -> + {ok, rcl_state:t()} | relcool:error(). +do_individual_overlay(State, OverlayVars, {mkdir, Dir}) -> + ModuleName = make_template_name("rcl_mkdir_template", Dir), + case erlydtl:compile(erlang:iolist_to_binary(Dir), ModuleName) of + {ok, ModuleName} -> + case render(ModuleName, OverlayVars) of + {ok, IoList} -> + Absolute = absolutize(State, + filename:join(rcl_state:output_dir(State), + erlang:iolist_to_binary(IoList))), + case rcl_util:mkdir_p(Absolute) of + {error, Error} -> + ?RCL_ERROR({unable_to_make_dir, Absolute, Error}); + ok -> + ok + end; + {error, Error} -> + ?RCL_ERROR({dir_render_failed, Dir, Error}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_compile_template, Dir, Reason}) + end; +do_individual_overlay(State, OverlayVars, {copy, From, To}) -> + FromTemplateName = make_template_name("rcl_copy_from_template", From), + ToTemplateName = make_template_name("rcl_copy_to_template", To), + file_render_do(OverlayVars, From, FromTemplateName, + fun(FromFile) -> + file_render_do(OverlayVars, To, ToTemplateName, + fun(ToFile) -> + copy_to(State, FromFile, ToFile) + end) + end); +do_individual_overlay(State, OverlayVars, {template, From, To}) -> + FromTemplateName = make_template_name("rcl_template_from_template", From), + ToTemplateName = make_template_name("rcl_template_to_template", To), + file_render_do(OverlayVars, From, FromTemplateName, + fun(FromFile) -> + file_render_do(OverlayVars, To, ToTemplateName, + fun(ToFile) -> + RelativeRoot = get_relative_root(State), + FromFile0 = absolutize(State, + filename:join(RelativeRoot, + erlang:iolist_to_binary(FromFile))), + FromFile1 = erlang:binary_to_list(FromFile0), + write_template(OverlayVars, + FromFile1, + absolutize(State, + filename:join(rcl_state:output_dir(State), + erlang:iolist_to_binary(ToFile)))) + end) + end). + +-spec copy_to(rcl_state:t(), file:name(), file:name()) -> ok | relcool:error(). +copy_to(State, FromFile0, ToFile0) -> + RelativeRoot = get_relative_root(State), + ToFile1 = absolutize(State, filename:join(rcl_state:output_dir(State), + erlang:iolist_to_binary(ToFile0))), + + FromFile1 = absolutize(State, filename:join(RelativeRoot, + erlang:iolist_to_binary(FromFile0))), + ToFile2 = case is_directory(ToFile0, ToFile1) of + false -> + filelib:ensure_dir(ToFile1), + ToFile1; + true -> + rcl_util:mkdir_p(ToFile1), + erlang:iolist_to_binary(filename:join(ToFile1, + filename:basename(FromFile1))) + end, + case ec_file:copy(FromFile1, ToFile2) of + ok -> + {ok, FileInfo} = file:read_file_info(FromFile1), + ok = file:write_file_info(ToFile2, FileInfo), + ok; + {error, Err} -> + ?RCL_ERROR({copy_failed, + FromFile1, + ToFile1, Err}) + end. + +get_relative_root(State) -> + case rcl_state:config_file(State) of + [] -> + rcl_state:root_dir(State); + Config -> + filename:dirname(Config) + end. + +-spec is_directory(file:name(), file:name()) -> boolean(). +is_directory(ToFile0, ToFile1) -> + case re:run(ToFile0, ?DIRECTORY_RE) of + nomatch -> + filelib:is_dir(ToFile1); + _ -> + true + end. + + +-spec render_template(proplists:proplist(), iolist()) -> + ok | relcool:error(). +render_template(OverlayVars, Data) -> + TemplateName = make_template_name("rcl_template_renderer", Data), + case erlydtl:compile(Data, TemplateName) of + Good when Good =:= ok; Good =:= {ok, TemplateName} -> + case render(TemplateName, OverlayVars) of + {ok, IoData} -> + {ok, IoData}; + {error, Reason} -> + ?RCL_ERROR({unable_to_render_template, Data, Reason}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_compile_template, Data, Reason}) + end. + +write_template(OverlayVars, FromFile, ToFile) -> + case render_template(OverlayVars, FromFile) of + {ok, IoData} -> + case filelib:ensure_dir(ToFile) of + ok -> + case file:write_file(ToFile, IoData) of + ok -> + {ok, FileInfo} = file:read_file_info(FromFile), + ok = file:write_file_info(ToFile, FileInfo), + ok; + {error, Reason} -> + ?RCL_ERROR({unable_to_write, ToFile, Reason}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason}) + end; + Error -> + Error + end. + +-spec file_render_do(proplists:proplist(), iolist(), module(), + fun((term()) -> {ok, rcl_state:t()} | relcool:error())) -> + {ok, rcl_state:t()} | relcool:error(). +file_render_do(OverlayVars, Data, TemplateName, NextAction) -> + case erlydtl:compile(erlang:iolist_to_binary(Data), TemplateName) of + {ok, TemplateName} -> + case render(TemplateName, OverlayVars) of + {ok, IoList} -> + NextAction(IoList); + {error, Error} -> + ?RCL_ERROR({render_failed, Data, Error}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_compile_template, Data, Reason}) + end. + +-spec make_template_name(string(), term()) -> module(). +make_template_name(Base, Value) -> + %% Seed so we get different values each time + random:seed(erlang:now()), + Hash = erlang:phash2(Value), + Ran = random:uniform(10000000), + erlang:list_to_atom(Base ++ "_" ++ + erlang:integer_to_list(Hash) ++ + "_" ++ erlang:integer_to_list(Ran)). + +-spec render(module(), proplists:proplist()) -> {ok, iolist()} | {error, Reason::term()}. +render(ModuleName, OverlayVars) -> + try + ModuleName:render(OverlayVars) + catch + _:Reason -> + {error, Reason} + end. + +absolutize(State, FileName) -> + filename:absname(filename:join(rcl_state:root_dir(State), + erlang:iolist_to_binary(FileName))). diff --git a/src/rlx_prv_release.erl b/src/rlx_prv_release.erl new file mode 100644 index 0000000..d90b492 --- /dev/null +++ b/src/rlx_prv_release.erl @@ -0,0 +1,187 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provider uses the lib_dir setting of the state. It searches the +%%% Lib Dirs looking for all OTP Applications that are available. When it finds +%%% those OTP Applications it loads the information about them and adds them to +%%% the state of available apps. This implements the rcl_provider behaviour. +-module(rcl_prv_release). + +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +do(State) -> + DepGraph = create_dep_graph(State), + find_default_release(State, DepGraph). + +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error(no_goals_specified) -> + "No goals specified for this release ~n"; +format_error({no_release_name, Vsn}) -> + io_lib:format("A target release version was specified (~s) but no name", [Vsn]); +format_error({invalid_release_info, Info}) -> + io_lib:format("Target release information is in an invalid format ~p", [Info]); +format_error({multiple_release_names, RelA, RelB}) -> + io_lib:format("No default release name was specified and there are multiple " + "releases in the config: ~s, ~s", + [RelA, RelB]); +format_error(no_releases_in_system) -> + "No releases have been specified in the system!"; +format_error({no_releases_for, RelName}) -> + io_lib:format("No releases exist in the system for ~s!", [RelName]); +format_error({release_not_found, {RelName, RelVsn}}) -> + io_lib:format("No releases exist in the system for ~p:~s!", [RelName, RelVsn]); +format_error({failed_solve, Error}) -> + io_lib:format("Failed to solve release:\n ~s", + [rcl_depsolver:format_error({error, Error})]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec create_dep_graph(rcl_state:t()) -> rcl_depsolver:t(). +create_dep_graph(State) -> + Apps = rcl_state:available_apps(State), + Graph0 = rcl_depsolver:new_graph(), + lists:foldl(fun(App, Graph1) -> + AppName = rcl_app_info:name(App), + AppVsn = rcl_app_info:vsn(App), + Deps = rcl_app_info:active_deps(App) ++ + rcl_app_info:library_deps(App), + rcl_depsolver:add_package_version(Graph1, + AppName, + AppVsn, + Deps) + end, Graph0, Apps). + + +-spec find_default_release(rcl_state:t(), rcl_depsolver:t()) -> + {ok, rcl_state:t()} | relcool:error(). +find_default_release(State, DepGraph) -> + case rcl_state:default_configured_release(State) of + {undefined, undefined} -> + resolve_default_release(State, DepGraph); + {RelName, undefined} -> + resolve_default_version(State, DepGraph, RelName); + {undefined, Vsn} -> + ?RCL_ERROR({no_release_name, Vsn}); + {RelName, RelVsn} -> + solve_release(State, DepGraph, RelName, RelVsn) + end. + +resolve_default_release(State0, DepGraph) -> + %% Here we will just get the highest versioned release and run that. + case lists:sort(fun release_sort/2, + ec_dictionary:to_list(rcl_state:configured_releases(State0))) of + [{{RelName, RelVsn}, _} | _] -> + State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), + solve_release(State1, DepGraph, RelName, RelVsn); + [] -> + ?RCL_ERROR(no_releases_in_system) + end. + +resolve_default_version(State0, DepGraph, RelName) -> + %% Here we will just get the lastest version and run that. + AllReleases = ec_dictionary:to_list(rcl_state:configured_releases(State0)), + SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases, + PossibleRelName =:= RelName], + case lists:sort(fun release_sort/2, SpecificReleases) of + [{{RelName, RelVsn}, _} | _] -> + State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), + solve_release(State1, DepGraph, RelName, RelVsn); + [] -> + ?RCL_ERROR({no_releases_for, RelName}) + end. + +-spec release_sort({{rcl_release:name(),rcl_release:vsn()}, term()}, + {{rcl_release:name(),rcl_release:vsn()}, term()}) -> + boolean(). +release_sort({{RelName, RelVsnA}, _}, + {{RelName, RelVsnB}, _}) -> + ec_semver:lte(RelVsnA, RelVsnB); +release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) -> + %% The release names are different. When the releases are named differently + %% we can not just take the lastest version. You *must* provide a default + %% release name at least. So we throw an error here that the top can catch + %% and return + erlang:atom_to_list(RelNameA) =< erlang:atom_to_list(RelNameB) andalso + ec_semver:lte(RelVsnA, RelVsnB). + +solve_release(State0, DepGraph, RelName, RelVsn) -> + rcl_log:debug(rcl_state:log(State0), + "Solving Release ~p-~s~n", + [RelName, RelVsn]), + try + Release = rcl_state:get_configured_release(State0, RelName, RelVsn), + Goals = rcl_release:goals(Release), + case Goals of + [] -> + ?RCL_ERROR(no_goals_specified); + _ -> + case rcl_depsolver:solve(DepGraph, Goals) of + {ok, Pkgs} -> + set_resolved(State0, Release, Pkgs); + {error, Error} -> + ?RCL_ERROR({failed_solve, Error}) + end + end + catch + throw:not_found -> + ?RCL_ERROR({release_not_found, RelName, RelVsn}) + end. + +set_resolved(State, Release0, Pkgs) -> + case rcl_release:realize(Release0, Pkgs, rcl_state:available_apps(State)) of + {ok, Release1} -> + rcl_log:info(rcl_state:log(State), + "Resolved ~p-~s~n", + [rcl_release:name(Release1), + rcl_release:vsn(Release1)]), + rcl_log:debug(rcl_state:log(State), + fun() -> + rcl_release:format(1, Release1) + end), + {ok, rcl_state:add_realized_release(State, Release1)}; + {error, E} -> + ?RCL_ERROR({release_error, E}) + end. + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +-endif. diff --git a/src/rlx_rel_discovery.erl b/src/rlx_rel_discovery.erl new file mode 100644 index 0000000..6cd84f0 --- /dev/null +++ b/src/rlx_rel_discovery.erl @@ -0,0 +1,156 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provider uses the lib_dir setting of the state. It searches the +%%% Lib Dirs looking for all OTP Applications that are available. When it finds +%%% those OTP Applications it loads the information about them and adds them to +%%% the state of available apps. This implements the rcl_provider behaviour. +-module(rcl_rel_discovery). + +-export([do/3, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t(), [filename:name()], [rcl_app_info:t()]) -> + {ok, [rcl_release:t()]} | relcool:error(). +do(State, LibDirs, AppMeta) -> + rcl_log:info(rcl_state:log(State), + fun() -> + ["Resolving available releases from directories:\n", + [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] + end), + resolve_rel_metadata(State, LibDirs, AppMeta). + +-spec format_error([ErrorDetail::term()]) -> iolist(). +format_error(ErrorDetails) + when erlang:is_list(ErrorDetails) -> + [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +resolve_rel_metadata(State, LibDirs, AppMeta) -> + ReleaseMeta0 = lists:flatten(rcl_dscv_util:do(fun(LibDir, FileType) -> + discover_dir(LibDir, + AppMeta, + FileType) + end, LibDirs)), + + Errors = [case El of + {error, Ret} -> Ret; + _ -> El + end + || El <- ReleaseMeta0, + case El of + {error, _} -> + true; + _ -> + false + end], + + case Errors of + [] -> + ReleaseMeta1 = [RelMeta || {ok, RelMeta} <- ReleaseMeta0], + rcl_log:debug(rcl_state:log(State), + fun() -> + ["Resolved the following OTP Releases from the system: \n", + [[rcl_release:format(1, Rel), "\n"] || Rel <- ReleaseMeta1]] + end), + {ok, ReleaseMeta1}; + _ -> + ?RCL_ERROR(Errors) + end. + +-spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({accessing, File, eaccess}) -> + io_lib:format("permission denied accessing file ~s", [File]); +format_detail({accessing, File, Type}) -> + io_lib:format("error (~p) accessing file ~s", [Type, File]). + +-spec discover_dir(file:name(), [rcl_app_info:t()], directory | file) -> + {ok, rcl_release:t()} + | {error, Reason::term()} + | {noresult, false}. +discover_dir(_File, _AppMeta, directory) -> + {noresult, true}; +discover_dir(File, AppMeta, file) -> + is_valid_release(File, AppMeta). + +-spec is_valid_release(file:name(), + [rcl_app_info:t()]) -> + {ok, rcl_release:t()} + | {error, Reason::term()} + | {noresult, false}. +is_valid_release(File, AppMeta) -> + case filename:extension(File) of + <<".rel">>-> + resolve_release(File, AppMeta); + _ -> + {noresult, false} + end. + +resolve_release(RelFile, AppMeta) -> + case file:consult(RelFile) of + {ok, [{release, {RelName, RelVsn}, + {erts, ErtsVsn}, + Apps}]} -> + build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta); + {ok, InvalidRelease} -> + ?RCL_ERROR({invalid_release_information, InvalidRelease}); + {error, Reason} -> + ?RCL_ERROR({unable_to_read, RelFile, Reason}) + end. + +build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> + Release = rcl_release:erts(rcl_release:new(RelName, RelVsn, RelFile), + ErtsVsn), + resolve_apps(Apps, AppMeta, Release, []). + +resolve_apps([], _AppMeta, Release, Acc) -> + {ok, rcl_release:application_details(Release, Acc)}; +resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> + AppName = erlang:element(1, AppInfo), + AppVsn = ec_semver:parse(erlang:element(2, AppInfo)), + case find_app(AppName, AppVsn, AppMeta) of + Error = {error, _} -> + Error; + ResolvedApp -> + resolve_apps(Apps, AppMeta, Release, [ResolvedApp | Acc]) + end. + +find_app(AppName, AppVsn, AppMeta) -> + case ec_lists:find(fun(App) -> + NAppName = rcl_app_info:name(App), + NAppVsn = rcl_app_info:vsn(App), + AppName == NAppName andalso + AppVsn == NAppVsn + end, AppMeta) of + {ok, Head} -> + Head; + error -> + ?RCL_ERROR({could_not_find, {AppName, AppVsn}}) + end. diff --git a/src/rlx_release.erl b/src/rlx_release.erl new file mode 100644 index 0000000..9ed741e --- /dev/null +++ b/src/rlx_release.erl @@ -0,0 +1,415 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This module represents a release and its metadata and is used to +%%% manipulate the release metadata. +-module(rcl_release). + +-export([new/2, + new/3, + relfile/1, + relfile/2, + erts/2, + erts/1, + goals/2, + goals/1, + name/1, + vsn/1, + realize/3, + applications/1, + application_details/1, + application_details/2, + realized/1, + metadata/1, + canonical_name/1, + format/1, + format/2, + format_error/1]). + +-export_type([t/0, + name/0, + vsn/0, + app_name/0, + app_vsn/0, + app_type/0, + application_spec/0, + application_goal/0]). + +-include_lib("relcool/include/relcool.hrl"). + +-record(release_t, {name :: atom(), + vsn :: ec_semver:any_version(), + erts :: ec_semver:any_version(), + goals = [] :: [rcl_depsolver:constraint()], + realized = false :: boolean(), + annotations = undefined :: annotations(), + applications = [] :: [application_spec()], + relfile :: undefined | string(), + app_detail = [] :: [rcl_app_info:t()]}). + +%%============================================================================ +%% types +%%============================================================================ +-type name() :: atom(). +-type vsn() :: string(). +-type app_name() :: atom(). +-type app_vsn() :: string(). +-type app_type() :: permanent | transient | temporary | load | none. +-type incl_apps() :: [app_name()]. + +-type application_spec() :: {app_name(), app_vsn()} | + {app_name(), app_vsn(), app_type() | incl_apps()} | + {app_name(), app_vsn(), app_type(), incl_apps()}. + +-type application_constraint() :: rcl_depsolver:raw_constraint() | string() | binary(). +-type application_goal() :: application_constraint() + | {application_constraint(), app_type() | incl_apps()} + | {application_constraint(), app_type(), incl_apps() | none}. + +-type annotations() :: ec_dictionary:dictionary(app_name(), + {app_type(), incl_apps() | none}). + + +-opaque t() :: record(release_t). + +%%============================================================================ +%% API +%%============================================================================ +-spec new(atom(), string(), undefined | file:name()) -> t(). +new(ReleaseName, ReleaseVsn, Relfile) -> + #release_t{name=to_atom(ReleaseName), vsn=ReleaseVsn, + relfile = Relfile, + annotations=ec_dictionary:new(ec_dict)}. + +-spec new(atom(), string()) -> t(). +new(ReleaseName, ReleaseVsn) -> + new(ReleaseName, ReleaseVsn, undefined). + + +-spec relfile(t()) -> file:name() | undefined. +relfile(#release_t{relfile=Relfile}) -> + Relfile. + +-spec relfile(t(), file:name()) -> t(). +relfile(Release, Relfile) -> + Release#release_t{relfile=Relfile}. + +-spec name(t()) -> atom(). +name(#release_t{name=Name}) -> + Name. + +-spec vsn(t()) -> string(). +vsn(#release_t{vsn=Vsn}) -> + Vsn. + +-spec erts(t(), app_vsn()) -> t(). +erts(Release, Vsn) -> + Release#release_t{erts=Vsn}. + +-spec erts(t()) -> app_vsn(). +erts(#release_t{erts=Vsn}) -> + Vsn. + +-spec goals(t(), [application_goal()]) -> {ok, t()} | relcool:error(). +goals(Release, Goals0) -> + lists:foldl(fun parse_goal0/2, + {ok, Release}, Goals0). + +-spec goals(t()) -> [application_goal()]. +goals(#release_t{goals=Goals}) -> + Goals. + +-spec realize(t(), [{app_name(), app_vsn()}], [rcl_app_info:t()]) -> + {ok, t()} | relcool:error(). +realize(Rel, Pkgs0, World0) -> + World1 = subset_world(Pkgs0, World0), + case rcl_topo:sort_apps(World1) of + {ok, Pkgs1} -> + process_specs(realize_erts(Rel), Pkgs1); + Error={error, _} -> + Error + end. + +%% @doc this gives the application specs for the release. This can only be +%% populated by the 'realize' call in this module. +-spec applications(t()) -> [application_spec()]. +applications(#release_t{applications=Apps}) -> + Apps. + +%% @doc this gives the rcl_app_info objects representing the applications in +%% this release. These should only be populated by the 'realize' call in this +%% module or by reading an existing rel file. +-spec application_details(t()) -> [rcl_app_info:t()]. +application_details(#release_t{app_detail=App}) -> + App. + +%% @doc this is only expected to be called by a process building a new release +%% from an existing rel file. +-spec application_details(t(), [rcl_app_info:t()]) -> t(). +application_details(Release, AppDetail) -> + Release#release_t{app_detail=AppDetail}. + +-spec realized(t()) -> boolean(). +realized(#release_t{realized=Realized}) -> + Realized. + +-spec metadata(t()) -> term(). +metadata(#release_t{name=Name, vsn=Vsn, erts=ErtsVsn, applications=Apps, + realized=Realized}) -> + case Realized of + true -> + {ok, {release, {erlang:atom_to_list(Name), Vsn}, {erts, ErtsVsn}, + Apps}}; + false -> + ?RCL_ERROR({not_realized, Name, Vsn}) + end. + +%% @doc produce the canonical name (-) for this release +-spec canonical_name(t()) -> string(). +canonical_name(#release_t{name=Name, vsn=Vsn}) -> + erlang:binary_to_list(erlang:iolist_to_binary([erlang:atom_to_list(Name), "-", + Vsn])). + +-spec format(t()) -> iolist(). +format(Release) -> + format(0, Release). + +-spec format(non_neg_integer(), t()) -> iolist(). +format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, + goals = Goals, applications=Apps}) -> + BaseIndent = rcl_util:indent(Indent), + [BaseIndent, "release: ", rcl_util:to_string(Name), "-", Vsn, "\n", + rcl_util:indent(Indent + 1), " erts-", ErtsVsn, + ", realized = ", erlang:atom_to_list(Realized), "\n", + BaseIndent, "goals: \n", + [[rcl_util:indent(Indent + 1), format_goal(Goal), ",\n"] || Goal <- Goals], + case Realized of + true -> + [BaseIndent, "applications: \n", + [[rcl_util:indent(Indent + 1), io_lib:format("~p", [App]), ",\n"] || + App <- Apps]]; + false -> + [] + end]. + +-spec format_goal(application_goal()) -> iolist(). +format_goal({Constraint, AppType}) -> + io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType}]); +format_goal({Constraint, AppType, AppInc}) -> + io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType, AppInc}]); +format_goal(Constraint) -> + rcl_depsolver:format_constraint(Constraint). + +-spec format_error(Reason::term()) -> iolist(). +format_error({topo_error, E}) -> + rcl_topo:format_error(E); +format_error({failed_to_parse, Con}) -> + io_lib:format("Failed to parse constraint ~p", [Con]); +format_error({invalid_constraint, _, Con}) -> + io_lib:format("Invalid constraint specified ~p", [Con]); +format_error({not_realized, Name, Vsn}) -> + io_lib:format("Unable to produce metadata release: ~p-~s has not been realized", + [Name, Vsn]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec realize_erts(t()) -> t(). +realize_erts(Rel=#release_t{erts=undefined}) -> + Rel#release_t{erts=erlang:system_info(version)}; +realize_erts(Rel) -> + Rel. + +-spec process_specs(t(), [rcl_app_info:t()]) -> + {ok, t()}. +process_specs(Rel=#release_t{annotations=Annots, + goals=Goals}, World) -> + ActiveApps = lists:flatten([rcl_app_info:active_deps(El) || El <- World] ++ + [case get_app_name(Goal) of + {error, _} -> []; + G -> G + end || Goal <- Goals]), + LibraryApps = lists:flatten([rcl_app_info:library_deps(El) || El <- World]), + Specs = [create_app_spec(Annots, App, ActiveApps, LibraryApps) || App <- World], + {ok, Rel#release_t{annotations=Annots, + applications=Specs, + app_detail=World, + realized=true}}. + +-spec create_app_spec(annotations(), rcl_app_info:t(), [app_name()], + [app_name()]) -> + application_spec(). +create_app_spec(Annots, App, ActiveApps, LibraryApps) -> + %% If the app only exists as a dependency in a library app then it should + %% get the 'load' annotation unless the release spec has provided something + %% else + AppName = rcl_app_info:name(App), + TypeAnnot = + case (lists:member(AppName, LibraryApps) and + (not lists:member(AppName, ActiveApps))) of + true -> + load; + false -> + none + end, + BaseAnnots = + try + case ec_dictionary:get(AppName, Annots) of + {none, Incld} -> + {TypeAnnot, Incld}; + Else -> + Else + end + catch + throw:not_found -> + {TypeAnnot, none} + end, + Vsn = rcl_app_info:vsn_as_string(App), + case BaseAnnots of + {none, none} -> + {AppName, Vsn}; + {Type, none} -> + {AppName, Vsn, Type}; + {none, Incld0} -> + {AppName, Vsn, Incld0}; + {Type, Incld1} -> + {AppName, Vsn, Type, Incld1} + end. + +-spec subset_world([{app_name(), app_vsn()}], [rcl_app_info:t()]) -> [rcl_app_info:t()]. +subset_world(Pkgs, World) -> + [get_app_info(Pkg, World) || Pkg <- Pkgs]. + +-spec get_app_info({app_name(), app_vsn()}, [rcl_app_info:t()]) -> rcl_app_info:t(). +get_app_info({PkgName, PkgVsn}, World) -> + {ok, WorldEl} = + ec_lists:find(fun(El) -> + rcl_app_info:name(El) =:= PkgName andalso + rcl_app_info:vsn(El) =:= PkgVsn + end, World), + WorldEl. + +parse_goal0({Constraint0, Annots}, {ok, Release}) + when Annots =:= permanent; + Annots =:= transient; + Annots =:= temporary; + Annots =:= load; + Annots =:= none -> + case parse_constraint(Constraint0) of + {ok, Constraint1} -> + parse_goal1(Release, Constraint1, {Annots, none}); + Error -> + Error + end; +parse_goal0({Constraint0, Annots, Incls}, {ok, Release}) + when (Annots =:= permanent orelse + Annots =:= transient orelse + Annots =:= temporary orelse + Annots =:= load orelse + Annots =:= none), + erlang:is_list(Incls) -> + case parse_constraint(Constraint0) of + {ok, Constraint1} -> + parse_goal1(Release, Constraint1, {Annots, Incls}); + Error -> + Error + end; +parse_goal0(Constraint0, {ok, Release}) -> + case parse_constraint(Constraint0) of + {ok, Constraint1} -> + parse_goal1(Release, Constraint1, {none, none}); + Error -> + Error + end; +parse_goal0(_, E = {error, _}) -> + E; +parse_goal0(Constraint, _) -> + ?RCL_ERROR({invalid_constraint, 1, Constraint}). + +parse_goal1(Release = #release_t{annotations=Annots, goals=Goals}, + Constraint, NewAnnots) -> + case get_app_name(Constraint) of + E1 = {error, _} -> + E1; + AppName -> + {ok, + Release#release_t{annotations=ec_dictionary:add(AppName, NewAnnots, Annots), + goals = [Constraint | Goals]}} + end. + +-spec parse_constraint(application_constraint()) -> + rcl_depsolver:constraint() | relcool:error(). +parse_constraint(Constraint0) + when erlang:is_list(Constraint0); erlang:is_binary(Constraint0) -> + case rcl_goal:parse(Constraint0) of + {fail, _} -> + ?RCL_ERROR({failed_to_parse, Constraint0}); + {ok, Constraint1} -> + {ok, Constraint1} + end; +parse_constraint(Constraint0) + when erlang:is_tuple(Constraint0); + erlang:is_atom(Constraint0) -> + Constraint1 = parse_version(Constraint0), + case rcl_depsolver:is_valid_constraint(Constraint1) of + false -> + ?RCL_ERROR({invalid_constraint, 2, Constraint0}); + true -> + {ok, Constraint1} + end; +parse_constraint(Constraint) -> + ?RCL_ERROR({invalid_constraint, 3, Constraint}). + +-spec get_app_name(rcl_depsolver:raw_constraint()) -> + AppName::atom() | relcool:error(). +get_app_name(AppName) when erlang:is_atom(AppName) -> + AppName; +get_app_name({AppName, _}) when erlang:is_atom(AppName) -> + AppName; +get_app_name({AppName, _, _}) when erlang:is_atom(AppName) -> + AppName; +get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) -> + AppName; +get_app_name(V) -> + ?RCL_ERROR({invalid_constraint, 4, V}). + +-spec parse_version(rcl_depsolver:raw_constraint()) -> + rcl_depsolver:constraint(). +parse_version({AppName, Version}) + when erlang:is_binary(Version); + erlang:is_list(Version) -> + {AppName, rcl_depsolver:parse_version(Version)}; +parse_version({AppName, Version, Constraint}) + when erlang:is_binary(Version); + erlang:is_list(Version) -> + {AppName, rcl_depsolver:parse_version(Version), Constraint}; +parse_version({AppName, Version, Constraint0, Constraint1}) + when erlang:is_binary(Version); + erlang:is_list(Version) -> + {AppName, rcl_depsolver:parse_version(Version), Constraint1, Constraint0}; +parse_version(Constraint) -> + Constraint. + +to_atom(RelName) + when erlang:is_list(RelName) -> + erlang:list_to_atom(RelName); +to_atom(Else) + when erlang:is_atom(Else) -> + Else. diff --git a/src/rlx_state.erl b/src/rlx_state.erl new file mode 100644 index 0000000..ca6ec8c --- /dev/null +++ b/src/rlx_state.erl @@ -0,0 +1,354 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Provides state management services for the relcool tool. Generally, +%%% those things that are fixed have a direct api. Those things that are mutable +%%% have a more mutable api. +-module(rcl_state). + +-export([new/2, + log/1, + action/1, + output_dir/1, + lib_dirs/1, + overrides/1, + overrides/2, + skip_apps/1, + skip_apps/2, + goals/1, + config_file/1, + config_file/2, + providers/1, + providers/2, + sys_config/1, + sys_config/2, + root_dir/1, + root_dir/2, + add_configured_release/2, + get_configured_release/3, + configured_releases/1, + realized_releases/1, + realized_releases/2, + add_realized_release/2, + get_realized_release/3, + update_realized_release/2, + default_configured_release/1, + default_configured_release/3, + available_apps/1, + available_apps/2, + get/2, + get/3, + put/3, + caller/1, + caller/2, + upfrom/1, + format/1, + format/2]). + + +-export_type([t/0, + releases/0, + cmd_args/0]). + +-record(state_t, {log :: rcl_log:t(), + root_dir :: file:name(), + caller :: caller(), + action :: atom(), + output_dir :: file:name(), + lib_dirs=[] :: [file:name()], + config_file=[] :: file:filename() | undefined, + goals=[] :: [rcl_depsolver:constraint()], + providers = [] :: [rcl_provider:t()], + available_apps = [] :: [rcl_app_info:t()], + default_configured_release :: {rcl_release:name(), rcl_release:vsn()}, + sys_config :: file:filename() | undefined, + overrides :: [{AppName::atom(), Directory::file:filename()}], + skip_apps = [] :: [AppName::atom()], + configured_releases :: releases(), + realized_releases :: releases(), + upfrom :: string() | binary() | undefined, + config_values :: ec_dictionary:dictionary(Key::atom(), + Value::term())}). + +%%============================================================================ +%% types +%%============================================================================ + +-type releases() :: ec_dictionary:dictionary({rcl_release:name(), + rcl_release:vsn()}, + rcl_release:t()). +-type cmd_args() :: proplists:proplist(). +-type caller() :: command_line | api. + +-opaque t() :: record(state_t). + +%%============================================================================ +%% API +%%============================================================================ +%% @doc Create a new 'log level' for the system +-spec new(proplists:proplist(), atom()) -> t(). +new(PropList, Target) + when erlang:is_list(PropList), + erlang:is_atom(Target) -> + {ok, Root} = file:get_cwd(), + State0 = + #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), + output_dir=proplists:get_value(output_dir, PropList, ""), + lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], + config_file=proplists:get_value(config, PropList, undefined), + action = Target, + caller = proplists:get_value(caller, PropList, api), + goals=proplists:get_value(goals, PropList, []), + providers = [], + configured_releases=ec_dictionary:new(ec_dict), + realized_releases=ec_dictionary:new(ec_dict), + config_values=ec_dictionary:new(ec_dict), + overrides = proplists:get_value(overrides, PropList, []), + root_dir = proplists:get_value(root_dir, PropList, Root), + upfrom = proplists:get_value(upfrom, PropList, undefined), + default_configured_release={proplists:get_value(relname, PropList, undefined), + proplists:get_value(relvsn, PropList, undefined)}}, + rcl_state:put(create_logic_providers(State0), + disable_default_libs, + proplists:get_value(disable_default_libs, PropList, false)). + +%% @doc the action targeted for this system +-spec action(t()) -> atom(). +action(#state_t{action=Action}) -> + Action. + +%% @doc the application overrides for the system +-spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}]. +overrides(#state_t{overrides=Overrides}) -> + Overrides. + +%% @doc the application overrides for the system +-spec overrides(t(), [{AppName::atom(), Directory::file:filename()}]) -> t(). +overrides(State, Overrides) -> + State#state_t{overrides=Overrides}. + + +-spec skip_apps(t()) -> [AppName::atom()]. +skip_apps(#state_t{skip_apps=Apps}) -> + Apps. + +%% @doc the application overrides for the system +-spec skip_apps(t(), [AppName::atom()]) -> t(). +skip_apps(State, SkipApps) -> + State#state_t{skip_apps=SkipApps}. + +%% @doc get the current log state for the system +-spec log(t()) -> rcl_log:t(). +log(#state_t{log=LogState}) -> + LogState. + +-spec output_dir(t()) -> file:name(). +output_dir(#state_t{output_dir=OutDir}) -> + OutDir. + +-spec lib_dirs(t()) -> [file:name()]. +lib_dirs(#state_t{lib_dirs=LibDir}) -> + LibDir. + +-spec goals(t()) -> [rcl_depsolver:constraint()]. +goals(#state_t{goals=TS}) -> + TS. + +-spec config_file(t()) -> file:filename() | undefined. +config_file(#state_t{config_file=ConfigFiles}) -> + ConfigFiles. + +-spec config_file(t(), file:filename() | undefined) -> t(). +config_file(State, ConfigFiles) -> + State#state_t{config_file=ConfigFiles}. + +-spec providers(t()) -> [rcl_provider:t()]. +providers(#state_t{providers=Providers}) -> + Providers. + +-spec sys_config(t()) -> file:filename() | undefined. +sys_config(#state_t{sys_config=SysConfig}) -> + SysConfig. + +-spec sys_config(t(), file:filename()) -> t(). +sys_config(State, SysConfig) -> + State#state_t{sys_config=SysConfig}. + +-spec root_dir(t()) -> file:filename() | undefined. +root_dir(#state_t{root_dir=RootDir}) -> + RootDir. + +-spec root_dir(t(), file:filename()) -> t(). +root_dir(State, RootDir) -> + State#state_t{root_dir=RootDir}. + +-spec providers(t(), [rcl_provider:t()]) -> t(). +providers(M, NewProviders) -> + M#state_t{providers=NewProviders}. + +-spec add_configured_release(t(), rcl_release:t()) -> t(). +add_configured_release(M=#state_t{configured_releases=Releases}, Release) -> + M#state_t{configured_releases=ec_dictionary:add({rcl_release:name(Release), + rcl_release:vsn(Release)}, + Release, + Releases)}. + +-spec get_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +get_configured_release(#state_t{configured_releases=Releases}, Name, Vsn) -> + ec_dictionary:get({Name, Vsn}, Releases). + +-spec configured_releases(t()) -> releases(). +configured_releases(#state_t{configured_releases=Releases}) -> + Releases. + +-spec realized_releases(t()) -> releases(). +realized_releases(#state_t{realized_releases=Releases}) -> + Releases. + +-spec realized_releases(t(), releases()) -> t(). +realized_releases(State, Releases) -> + State#state_t{realized_releases=Releases}. + +-spec add_realized_release(t(), rcl_release:t()) -> t(). +add_realized_release(State = #state_t{realized_releases=Releases}, Release) -> + NewReleases = ec_dictionary:add({rcl_release:name(Release), rcl_release:vsn(Release)}, + Release, Releases), + State#state_t{realized_releases=NewReleases}. + +-spec get_realized_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +get_realized_release(#state_t{realized_releases=Releases}, Name, Vsn) -> + ec_dictionary:get({Name, Vsn}, Releases). + +-spec update_realized_release(t(), rcl_release:t()) -> + t(). +update_realized_release(M=#state_t{realized_releases=Releases}, Release) -> + M#state_t{realized_releases=ec_dictionary:add({rcl_release:name(Release), + rcl_release:vsn(Release)}, + Release, + Releases)}. + +-spec default_configured_release(t()) -> + {rcl_release:name() | undefined, rcl_release:vsn() | undefined}. +default_configured_release(#state_t{default_configured_release=Def}) -> + Def. + +-spec default_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). +default_configured_release(M, Name, Vsn) -> + M#state_t{default_configured_release={Name, Vsn}}. + +-spec available_apps(t()) -> [rcl_app_info:t()]. +available_apps(#state_t{available_apps=Apps}) -> + Apps. + +-spec available_apps(t(), [rcl_app_info:t()]) -> t(). +available_apps(M, NewApps) -> + M#state_t{available_apps=NewApps}. + +-spec get(t(), atom()) -> term(). +get(#state_t{config_values=Config}, Key) + when erlang:is_atom(Key) -> + ec_dictionary:get(Key, Config). + +-spec get(t(), atom(), DefaultValue::term()) -> term(). +get(#state_t{config_values=Config}, Key, DefaultValue) + when erlang:is_atom(Key) -> + try + ec_dictionary:get(Key, Config) + catch + throw:not_found -> + DefaultValue + end. + +-spec put(t(), atom(), term()) ->t(). +put(M=#state_t{config_values=Config}, Key, Value) + when erlang:is_atom(Key) -> + M#state_t{config_values=ec_dictionary:add(Key, Value, Config)}. + +-spec caller(t()) -> caller(). +caller(#state_t{caller=Caller}) -> + Caller. + +-spec caller(t(), caller()) -> t(). +caller(S, Caller) -> + S#state_t{caller=Caller}. + +-spec upfrom(t()) -> string() | binary() | undefined. +upfrom(#state_t{upfrom=UpFrom}) -> + UpFrom. + +-spec format(t()) -> iolist(). +format(Mod) -> + format(Mod, 0). + +-spec format(t(), non_neg_integer()) -> iolist(). +format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, + caller=Caller, config_values=Values0, + goals=Goals, config_file=ConfigFile, + providers=Providers}, + Indent) -> + Values1 = ec_dictionary:to_list(Values0), + [rcl_util:indent(Indent), + <<"state(">>, erlang:atom_to_list(Caller), <<"):\n">>, + rcl_util:indent(Indent + 1), <<"log: ">>, rcl_log:format(LogState), <<",\n">>, + rcl_util:indent(Indent + 1), "config file: ", rcl_util:optional_to_string(ConfigFile), "\n", + rcl_util:indent(Indent + 1), "goals: \n", + [[rcl_util:indent(Indent + 2), rcl_depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], + rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", + rcl_util:indent(Indent + 1), "lib_dirs: \n", + [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs], + rcl_util:indent(Indent + 1), "providers: \n", + [[rcl_util:indent(Indent + 2), rcl_provider:format(Provider), ",\n"] || Provider <- Providers], + rcl_util:indent(Indent + 1), "provider config values: \n", + [[rcl_util:indent(Indent + 2), io_lib:format("~p", [Value]), ",\n"] || Value <- Values1]]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== + +-spec create_logic_providers(t()) -> t(). +create_logic_providers(State0) -> + {ConfigProvider, {ok, State1}} = rcl_provider:new(rcl_prv_config, State0), + {DiscoveryProvider, {ok, State2}} = rcl_provider:new(rcl_prv_discover, State1), + {ReleaseProvider, {ok, State3}} = rcl_provider:new(rcl_prv_release, State2), + {OverlayProvider, {ok, State4}} = rcl_provider:new(rcl_prv_overlay, State3), + {AssemblerProvider, {ok, State5}} = rcl_provider:new(rcl_prv_assembler, State4), + State5#state_t{providers=[ConfigProvider, DiscoveryProvider, + ReleaseProvider, OverlayProvider, AssemblerProvider]}. + +to_binary(Dir) + when erlang:is_list(Dir) -> + erlang:list_to_binary(Dir); +to_binary(Dir) + when erlang:is_binary(Dir) -> + Dir. + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +new_test() -> + LogState = rcl_log:new(error), + RCLState = new([{log, LogState}], release), + ?assertMatch(LogState, log(RCLState)). + +-endif. diff --git a/src/rlx_topo.erl b/src/rlx_topo.erl new file mode 100644 index 0000000..462b7c5 --- /dev/null +++ b/src/rlx_topo.erl @@ -0,0 +1,217 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Joe Armstrong +%%% @author Eric Merritt +%%% @doc +%%% This is a pretty simple topological sort for erlang. It was +%%% originally written for ermake by Joe Armstrong back in '98. It +%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relcool. +%%% +%%% A partial order on the set S is a set of pairs {Xi,Xj} such that +%%% some relation between Xi and Xj is obeyed. +%%% +%%% A topological sort of a partial order is a sequence of elements +%%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial +%%% order i < j +%%% @end +%%%------------------------------------------------------------------- +-module(rcl_topo). + +-export([sort_apps/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%==================================================================== +%% Types +%%==================================================================== +-type pair() :: {DependentApp::atom(), PrimaryApp::atom()}. +-type name() :: AppName::atom(). +-type element() :: name() | pair(). + +%%==================================================================== +%% API +%%==================================================================== + +%% @doc This only does a topo sort on the list of applications and +%% assumes that there is only *one* version of each app in the list of +%% applications. This implies that you have already done the +%% constraint solve before you pass the list of apps here to be +%% sorted. +-spec sort_apps([rcl_app_info:t()]) -> + {ok, [rcl_app_info:t()]} | + relcool:error(). +sort_apps(Apps) -> + Pairs = apps_to_pairs(Apps), + case sort(Pairs) of + {ok, Names} -> + {ok, names_to_apps(Names, Apps)}; + E -> + E + end. +%% @doc nicely format the error from the sort. +-spec format_error(Reason::term()) -> iolist(). +format_error({cycle, Pairs}) -> + ["Cycle detected in dependency graph, this must be resolved " + "before we can continue:\n", + case Pairs of + [{P1, P2}] -> + [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1)]; + [{P1, P2} | Rest] -> + [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1), + [["-> ", erlang:atom_to_list(PP2), " -> ", erlang:atom_to_list(PP1)] || {PP1, PP2} <- Rest]]; + [] -> + [] + end]. + +%%==================================================================== +%% Internal Functions +%%==================================================================== +%% @doc Do a topological sort on the list of pairs. +-spec sort([pair()]) -> {ok, [atom()]} | relcool:error(). +sort(Pairs) -> + iterate(Pairs, [], all(Pairs)). + +-spec names_to_apps([atom()], [rcl_app_info:t()]) -> [rcl_app_info:t()]. +names_to_apps(Names, Apps) -> + [find_app_by_name(Name, Apps) || Name <- Names]. + +-spec find_app_by_name(atom(), [rcl_app_info:t()]) -> rcl_app_info:t(). +find_app_by_name(Name, Apps) -> + {ok, App1} = + ec_lists:find(fun(App) -> + rcl_app_info:name(App) =:= Name + end, Apps), + App1. + +-spec apps_to_pairs([rcl_app_info:t()]) -> [pair()]. +apps_to_pairs(Apps) -> + lists:flatten([app_to_pairs(App) || App <- Apps]). + +-spec app_to_pairs(rcl_app_info:t()) -> [pair()]. +app_to_pairs(App) -> + [{DepApp, rcl_app_info:name(App)} || + DepApp <- + rcl_app_info:active_deps(App) ++ + rcl_app_info:library_deps(App)]. + + +%% @doc Iterate over the system. @private +-spec iterate([pair()], [name()], [name()]) -> + {ok, [name()]} | relcool:error(). +iterate([], L, All) -> + {ok, remove_duplicates(L ++ subtract(All, L))}; +iterate(Pairs, L, All) -> + case subtract(lhs(Pairs), rhs(Pairs)) of + [] -> + ?RCL_ERROR({cycle, Pairs}); + Lhs -> + iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) + end. + +-spec all([pair()]) -> [atom()]. +all(L) -> + lhs(L) ++ rhs(L). + +-spec lhs([pair()]) -> [atom()]. +lhs(L) -> + [X || {X, _} <- L]. + +-spec rhs([pair()]) -> [atom()]. +rhs(L) -> + [Y || {_, Y} <- L]. + +%% @doc all the elements in L1 which are not in L2 +%% @private +-spec subtract([element()], [element()]) -> [element()]. +subtract(L1, L2) -> + [X || X <- L1, not lists:member(X, L2)]. + +%% @doc remove dups from the list. @private +-spec remove_duplicates([element()]) -> [element()]. +remove_duplicates([H|T]) -> + case lists:member(H, T) of + true -> + remove_duplicates(T); + false -> + [H|remove_duplicates(T)] + end; +remove_duplicates([]) -> + []. + +%% @doc +%% removes all pairs from L2 where the first element +%% of each pair is a member of L1 +%% +%% L2' L1 = [X] L2 = [{X,Y}]. +%% @private +-spec remove_pairs([atom()], [pair()]) -> [pair()]. +remove_pairs(L1, L2) -> + [All || All={X, _Y} <- L2, not lists:member(X, L1)]. + +%%==================================================================== +%% Tests +%%==================================================================== +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +topo_1_test() -> + Pairs = [{one,two},{two,four},{four,six}, + {two,ten},{four,eight}, + {six,three},{one,three}, + {three,five},{five,eight}, + {seven,five},{seven,nine}, + {nine,four},{nine,ten}], + ?assertMatch({ok, [one,seven,two,nine,four,six,three,five,eight,ten]}, + sort(Pairs)). +topo_2_test() -> + Pairs = [{app2, app1}, {zapp1, app1}, {stdlib, app1}, + {app3, app2}, {kernel, app1}, {kernel, app3}, + {app2, zapp1}, {app3, zapp1}, {zapp2, zapp1}], + ?assertMatch({ok, [stdlib, kernel, zapp2, + app3, app2, zapp1, app1]}, + sort(Pairs)). + +topo_pairs_cycle_test() -> + Pairs = [{app2, app1}, {app1, app2}, {stdlib, app1}], + ?assertMatch({error, {_, {cycle, [{app2, app1}, {app1, app2}]}}}, + sort(Pairs)). + +topo_apps_cycle_test() -> + {ok, App1} = rcl_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]), + {ok, App2} = rcl_app_info:new(app2, "0.1", "/no-dir", [app1], []), + Apps = [App1, App2], + ?assertMatch({error, {_, {cycle, [{app2,app1},{app1,app2}]}}}, + sort_apps(Apps)). + +topo_apps_good_test() -> + Apps = [App || + {ok, App} <- + [rcl_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]), + rcl_app_info:new(app2, "0.1", "/no-dir", [app3], []), + rcl_app_info:new(app3, "0.1", "/no-dir", [kernel], []), + rcl_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []), + rcl_app_info:new(stdlib, "0.1", "/no-dir", [], []), + rcl_app_info:new(kernel, "0.1", "/no-dir", [], []), + rcl_app_info:new(zapp2, "0.1", "/no-dir", [], [])]], + {ok, Sorted} = sort_apps(Apps), + ?assertMatch([stdlib, kernel, zapp2, + app3, app2, zapp1, app1], + [rcl_app_info:name(App) || App <- Sorted]). + +-endif. diff --git a/src/rlx_util.erl b/src/rlx_util.erl new file mode 100644 index 0000000..4afb26d --- /dev/null +++ b/src/rlx_util.erl @@ -0,0 +1,93 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Trivial utility file to help handle common tasks +-module(rcl_util). + +-export([mkdir_p/1, + to_binary/1, + to_string/1, + is_error/1, + error_reason/1, + indent/1, + optional_to_string/1]). + +-define(ONE_LEVEL_INDENT, " "). +%%============================================================================ +%% types +%%============================================================================ + + +%%============================================================================ +%% API +%%============================================================================ +%% @doc Makes a directory including parent dirs if they are missing. +-spec mkdir_p(string()) -> ok | {error, Reason::file:posix()}. +mkdir_p(Path) -> + %% We are exploiting a feature of ensuredir that that creates all + %% directories up to the last element in the filename, then ignores + %% that last element. This way we ensure that the dir is created + %% and not have any worries about path names + DirName = filename:join([filename:absname(Path), "tmp"]), + filelib:ensure_dir(DirName). + +%% @doc ident to the level specified +-spec indent(non_neg_integer()) -> iolist(). +indent(Amount) when erlang:is_integer(Amount) -> + [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. + +-spec to_binary(iolist() | binary()) -> binary(). +to_binary(String) when erlang:is_list(String) -> + erlang:iolist_to_binary(String); +to_binary(Bin) when erlang:is_binary(Bin) -> + Bin. + +to_string(Binary) when erlang:is_binary(Binary) -> + erlang:binary_to_list(Binary); +to_string(Atom) when erlang:is_atom(Atom) -> + erlang:atom_to_list(Atom); +to_string(Else) when erlang:is_list(Else) -> + Else. + +%% @doc get the reason for a particular relcool error +-spec error_reason(relcool:error()) -> any(). +error_reason({error, {_, Reason}}) -> + Reason. +%% @doc check to see if the value is a relcool error +-spec is_error(relcool:error() | any()) -> boolean(). +is_error({error, _}) -> + true; +is_error(_) -> + false. + +%% @doc convert optional argument to empty string if undefined +optional_to_string(undefined) -> + ""; +optional_to_string(Value) when is_list(Value) -> + Value. + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +-endif. diff --git a/test/rcl_depsolver_tester.erl b/test/rcl_depsolver_tester.erl deleted file mode 100644 index 53f5ac0..0000000 --- a/test/rcl_depsolver_tester.erl +++ /dev/null @@ -1,474 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- -%% ex: ts=4 sx=4 et -%%------------------------------------------------------------------- -%% -%% Copyright 2012 Opscode, Inc. All Rights Reserved. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% -%% @author Eric Merritt -%% @doc -%% Additional testing for depsolver -%% @end -%%------------------------------------------------------------------- --module(rcl_depsolver_tester). - --export([run_data/1, run_log/1]). --include_lib("eunit/include/eunit.hrl"). - --define(ADD_PKG, "^DepSelector\\sinst#\\s(\\d+)\\s-\\s" - "Adding\\spackage\\sid\\s(\\d+)\\/(\\d+):\\smin\\s=\\s-1," - "\\smax\\s=\\s(\\d+),\\scurrent\\sversion\\s0$"). --define(ADD_VC, "^DepSelector\\sinst#\\s(\\d+)\\s-\\sAdding\\sVC\\s" - "for\\s(\\d+)\\s@\\s(\\d+)\\sdepPkg\\s(\\d+)\\s\\[\\s(\\d+)" - "\\s(\\d+)\\s\\]$"). --define(ADD_GOAL, "^DepSelector\\sinst#\\s(\\d+)\\s-\\s" - "Marking\\sPackage\\sRequired\\s(\\d+)$"). - -%%============================================================================ -%% Public Api -%%============================================================================ -run_data(FileName) -> - {ok, Device} = file:open(FileName, [read]), - run_data_file(Device). - -run_log(FileName) -> - {ok, Device} = file:open(FileName, [read]), - run_log_file(Device). - -data1_test() -> - ExpectedResult = versionify([{"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}]), - ?assertMatch({ok, ExpectedResult}, - run_data(fix_rebar_brokenness("data1.txt"))). - -data2_test() -> - ExpectedResult = versionify([{"app18","0.0.1"}, - {"app4","0.0.1"}, - {"app1","0.0.1"}, - {"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg5","0.0.2"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}, - {"dep_pkg16","1.0.2"}]), - ?assertMatch({ok, ExpectedResult}, - run_data(fix_rebar_brokenness("data2.txt"))). - -data3_test() -> - ExpectedResult = versionify([{"app68","0.0.1"}, - {"app58","0.0.1"}, - {"app48","0.0.7"}, - {"app38","0.0.1"}, - {"app28","0.0.1"}, - {"app18","0.0.1"}, - {"app4","0.0.1"}, - {"app1","0.0.1"}, - {"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg5","0.0.2"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}, - {"dep_pkg16","1.0.2"}]), - ?assertMatch({ok,ExpectedResult}, run_data(fix_rebar_brokenness("data3.txt"))). - -data4_test() -> - ExpectedResult = versionify([{"dep_pkg20","0.0.2"}, - {"app78","0.0.1"}, - {"app68","0.0.1"}, - {"app58","0.0.1"}, - {"app48","0.0.7"}, - {"app38","0.0.1"}, - {"app28","0.0.1"}, - {"app18","0.0.1"}, - {"app4","0.0.1"}, - {"app1","0.0.1"}, - {"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg5","0.0.2"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}, - {"dep_pkg16","1.0.2"}]), - ?assertMatch({ok, ExpectedResult}, - run_data(fix_rebar_brokenness("data4.txt"))). - -data5_test() -> - ExpectedResult = versionify([{"dep_pkg14","0.0.2"}, - {"dep_pkg22","0.0.2"}, - {"dep_pkg20","0.0.2"}, - {"app78","0.0.1"}, - {"app68","0.0.1"}, - {"app58","0.0.1"}, - {"app48","0.0.7"}, - {"app38","0.0.1"}, - {"app28","0.0.1"}, - {"app18","0.0.1"}, - {"app4","0.0.1"}, - {"app1","0.0.1"}, - {"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg5","0.0.2"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}, - {"dep_pkg16","1.0.2"}]), - ?assertMatch({ok, ExpectedResult}, - run_data(fix_rebar_brokenness("data5.txt"))). - -data6_test() -> - ExpectedResult = versionify([{"app108","0.0.1"}, - {"app98","0.0.1"}, - {"app88","0.0.1"}, - {"dep_pkg14","0.0.2"}, - {"dep_pkg22","0.0.2"}, - {"dep_pkg20","0.0.2"}, - {"app78","0.0.1"}, - {"app68","0.0.1"}, - {"app58","0.0.1"}, - {"app48","0.0.7"}, - {"app38","0.0.1"}, - {"app28","0.0.1"}, - {"app18","0.0.1"}, - {"app4","0.0.1"}, - {"app1","0.0.1"}, - {"app6","0.0.1"}, - {"dep_pkg13","0.0.2"}, - {"app13","0.0.1"}, - {"dep_pkg5","0.0.2"}, - {"dep_pkg1","0.0.2"}, - {"dep_pkg2","0.0.5"}, - {"dep_pkg7","0.1.2"}, - {"app9","0.0.1"}, - {"dep_pkg16","1.0.2"}]), - ?assertMatch({ok, ExpectedResult}, - run_data(fix_rebar_brokenness("data6.txt"))). - -log_07be9e47_test() -> - Data = run_log(fix_rebar_brokenness("log-07be9e47-6f42-4a5d-b8b5-1d2eae1ad83b.txt")), - ExpectedResult = versionify([{"0","0"}, - {"1","0"}, - {"3","0"}, - {"4","0"}, - {"5","0"}, - {"6","0"}, - {"7","0"}, - {"8","0"}, - {"9","0"}, - {"10","0"}, - {"11","0"}, - {"12","0"}, - {"13","0"}, - {"14","0"}, - {"15","0"}, - {"16","0"}, - {"18","0"}, - {"19","0"}, - {"21","0"}, - {"22","0"}, - {"23","0"}, - {"24","0"}, - {"25","0"}]), - ?assertMatch({ok, ExpectedResult}, - Data). - -log_183998c1_test() -> - ?assertMatch({error, {unreachable_package,<<"9">>}}, - run_log(fix_rebar_brokenness("log-183998c1-2ada-4214-b308-e480345c42f2.txt"))). - - -log_311a15e7_test() -> - {ok, Data} = run_log(fix_rebar_brokenness("log-311a15e7-3378-4c5b-beb7-86a1b9cf0ea9.txt")), - ExpectedResult = lists:sort(versionify([{"45", "22"}, - {"40","1"}, - {"3","5"}, - {"9","0"}, - {"8","0"}, - {"7","0"}, - {"6","2"}, - {"1","5"}, - {"0","2"}, - {"61","1"}, - {"60","0"}, - {"35","4"}, - {"39","0"}, - {"38","2"}, - {"37","2"}, - {"36","3"}, - {"32","24"}, - {"30","0"}, - {"19","1"}, - {"18","0"}, - {"17","2"}, - {"16","0"}, - {"15","0"}, - {"14","1"}, - {"13","0"}, - {"12","1"}, - {"11","0"}, - {"10","1"}, - {"59","0"}, - {"58","1"}, - {"57","0"}, - {"56","0"}, - {"55","4"}, - {"29","2"}, - {"27","2"}, - {"26","0"}, - {"25","5"}, - {"24","3"}, - {"23","1"}, - {"22","3"}, - {"21","2"}, - {"20","0"}])), - ?assertMatch(ExpectedResult, lists:sort(Data)). - -log_382cfe5b_test() -> - {ok, Data} = - run_log(fix_rebar_brokenness("log-382cfe5b-0ac2-48b8-83d1-717cb4620990.txt")), - ExpectedResult = lists:sort(versionify([{"18","0"}, - {"17","0"}, - {"15","1"}, - {"14","0"}, - {"10","0"}, - {"7","0"}, - {"6","0"}, - {"5","0"}, - {"4","0"}, - {"3","0"}, - {"2","1"}, - {"1","0"}, - {"0","0"}])), - ?assertMatch(ExpectedResult, lists:sort(Data)). - -log_d3564ef6_test() -> - {ok, Data} = run_log(fix_rebar_brokenness("log-d3564ef6-6437-41e7-90b6-dbdb849551a6_mod.txt")), - ExpectedResult = lists:sort(versionify([{"57","5"}, - {"56","3"}, - {"55","4"}, - {"54","0"}, - {"53","1"}, - {"82","0"}, - {"81","0"}, - {"80","1"}, - {"29","0"}, - {"28","5"}, - {"27","3"}, - {"26","1"}, - {"25","3"}, - {"24","2"}, - {"23","0"}, - {"22","1"}, - {"21","0"}, - {"20","2"}, - {"75","32"}, - {"79","2"}, - {"78","4"}, - {"74","7"}, - {"73","11"}, - {"72","0"}, - {"70","1"}, - {"47","4"}, - {"45","1"}, - {"44","1"}, - {"43","7"}, - {"42","1"}, - {"41","2"}, - {"40","2"}, - {"19","0"}, - {"18","0"}, - {"17","1"}, - {"16","0"}, - {"15","1"}, - {"14","0"}, - {"13","1"}, - {"12","0"}, - {"11","0"}, - {"10","0"}, - {"9","2"}, - {"4","5"}, - {"3","2"}, - {"0","3"}, - {"69","0"}, - {"68","1"}, - {"67","7"}, - {"39","3"}, - {"35","24"}, - {"33","0"}, - {"32","2"}, - {"30","2"}])), - ?assertMatch(ExpectedResult, lists:sort(Data)). - -log_ea2d264b_test() -> - {ok, Data} = run_log(fix_rebar_brokenness("log-ea2d264b-003e-4611-94ed-14efc7732083.txt")), - ExpectedResult = lists:sort(versionify([{"18","1"}, - {"17","0"}, - {"16","0"}, - {"15","0"}, - {"14","0"}, - {"13","1"}, - {"10","1"}, - {"9","1"}, - {"8","2"}, - {"6","0"}, - {"5","0"}, - {"4","0"}, - {"3","0"}, - {"2","0"}, - {"1","0"}, - {"0","1"}])), - ?assertMatch(ExpectedResult, lists:sort(Data)). - -%%============================================================================ -%% Internal Functions -%%============================================================================ -versionify(X) when erlang:is_list(X) -> - lists:map(fun versionify/1, X); -versionify({K, V}) -> - {erlang:list_to_binary(K), rcl_depsolver:parse_version(V)}. - -fix_rebar_brokenness(Filename) -> - Alt1 = filename:join(["./test", "data", Filename]), - Alt2 = filename:join(["../test", "data", Filename]), - case filelib:is_regular(Alt1) of - true -> - Alt1; - false -> - case filelib:is_regular(Alt2) of - true -> - Alt2; - false -> - io:format("~p~n", [Alt2]), - erlang:throw(unable_to_find_data_files) - end - end. - -run_data_file(Device) -> - Constraints = get_constraints(io:get_line(Device, "")), - rcl_depsolver:solve(process_packages(read_packages(Device)), Constraints). - -goble_lines(_Device, eof, Acc) -> - lists:reverse(Acc); -goble_lines(_Device, {error, Err}, _Acc) -> - erlang:throw(Err); -goble_lines(Device, ValidVal, Acc) -> - goble_lines(Device, io:get_line(Device, ""), [ValidVal | Acc]). - -goble_lines(Device) -> - goble_lines(Device, io:get_line(Device, ""), []). - -run_log_file(Device) -> - State0 = rcl_depsolver:new_graph(), - {Goals, State2} = - lists:foldl(fun(Line, Data) -> - process_add_goal(Line, - process_add_constraint(Line, - process_add_package(Line, Data))) - end, {[], State0}, goble_lines(Device)), - rcl_depsolver:solve(State2, Goals). - -read_packages(Device) -> - process_line(Device, io:get_line(Device, ""), []). - -process_line(Device, eof, Acc) -> - file:close(Device), - Acc; -process_line(Device, [], Acc) -> - process_line(Device, io:get_line(Device, ""), - Acc); -process_line(Device, "\n", Acc) -> - process_line(Device, io:get_line(Device, ""), - Acc); -process_line(Device, [$\s | Rest], [{Pkg, Vsn, Deps} | Acc]) -> - [DepPackage, Type, DepVsn] = string:tokens(Rest, " \n"), - Dep = - case Type of - "=" -> - {DepPackage, DepVsn}; - ">=" -> - {DepPackage, DepVsn, gte} - end, - process_line(Device, io:get_line(Device, ""), - [{Pkg, Vsn, [Dep | Deps]} | Acc]); -process_line(Device, Pkg, Acc) -> - [Package, Vsn] = string:tokens(Pkg, " \n"), - process_line(Device, io:get_line(Device, ""), - [{Package, Vsn, []} | Acc]). - -process_packages(Pkgs) -> - lists:foldl(fun({Pkg, Vsn, Constraints}, Dom0) -> - rcl_depsolver:add_package_version(Dom0, Pkg, Vsn, Constraints) - end, rcl_depsolver:new_graph(), Pkgs). - -get_constraints(ConLine) -> - AppVsns = string:tokens(ConLine, " \n"), - lists:map(fun(AppCon) -> - parse_app(AppCon, []) - end, AppVsns). -parse_app([$= | Rest], Acc) -> - {lists:reverse(Acc), Rest}; -parse_app([$>, $= | Rest], Acc) -> - {lists:reverse(Acc), Rest, gte}; -parse_app([Else | Rest], Acc) -> - parse_app(Rest, [Else | Acc]); -parse_app([], Acc) -> - lists:reverse(Acc). - -process_add_package(Line, {Goals, State0}) -> - case re:run(Line, ?ADD_PKG, [{capture, all, list}]) of - {match, [_All, _InstNumber, PkgName, _PkgCount, VersionCount]} -> - {Goals, - lists:foldl(fun(PkgVsn, State1) -> - rcl_depsolver:add_package_version(State1, - PkgName, - erlang:integer_to_list(PkgVsn), - []) - end, State0, lists:seq(0, - erlang:list_to_integer(VersionCount)))}; - _ -> - {Goals, State0} - end. - -process_add_constraint(Line, {Goals, State0}) -> - case re:run(Line, ?ADD_VC, [{capture, all, list}]) of - {match, [_All, _InstNumber, Pkg, Vsn, Dep, _Ignore, DepVsn]} -> - {Goals, - rcl_depsolver:add_package_version(State0, Pkg, Vsn, [{Dep, DepVsn}])}; - _ -> - {Goals, State0} - end. - -process_add_goal(Line, {Goals, State0}) -> - case re:run(Line, ?ADD_GOAL, [{capture, all, list}]) of - {match,[_All, _InstNumber, NewGoal]} -> - {[NewGoal | Goals], State0}; - _ -> - {Goals, State0} - end. diff --git a/test/rcl_depsolver_tests.erl b/test/rcl_depsolver_tests.erl deleted file mode 100644 index eae31a4..0000000 --- a/test/rcl_depsolver_tests.erl +++ /dev/null @@ -1,495 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- -%% ex: ts=4 sx=4 et -%% -%%------------------------------------------------------------------- -%% Copyright 2012 Opscode, Inc. All Rights Reserved. -%% -%% This file is provided to you under the Apache License, -%% Version 2.0 (the "License"); you may not use this file -%% except in compliance with the License. You may obtain -%% a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% -%% @author Eric Merritt -%%------------------------------------------------------------------- --module(rcl_depsolver_tests). - --include_lib("eunit/include/eunit.hrl"). - -%%============================================================================ -%% Tests -%%============================================================================ - -first_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2+build.33"}, - {app3, "0.2", '>='}]}, - {"0.2", []}, - {"0.3", []}]}, - {app2, [{"0.1", []}, - {"0.2+build.33",[{app3, "0.3"}]}, - {"0.3", []}]}, - {app3, [{"0.1", []}, - {"0.2", []}, - {"0.3", []}]}]), - - - case rcl_depsolver:solve(Dom0, [{app1, "0.1"}]) of - {ok,[{app3,{{0,3},{[],[]}}}, - {app2,{{0,2},{[],[<<"build">>,33]}}}, - {app1,{{0,1},{[],[]}}}]} -> - ok; - E -> - erlang:throw({invalid_result, E}) - end. - -second_test() -> - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.1", '>='}, - {app4, "0.2"}, - {app3, "0.2", '>='}]}, - {"0.2", []}, - {"0.3", []}]}, - {app2, [{"0.1", [{app3, "0.2", gte}]}, - {"0.2", [{app3, "0.2", gte}]}, - {"0.3", [{app3, "0.2", '>='}]}]}, - {app3, [{"0.1", [{app4, "0.2", '>='}]}, - {"0.2", [{app4, "0.2"}]}, - {"0.3", []}]}, - {app4, [{"0.1", []}, - {"0.2", [{app2, "0.2", gte}, - {app3, "0.3"}]}, - {"0.3", []}]}]), - - X = rcl_depsolver:solve(Dom0, [{app1, "0.1"}, - {app2, "0.3"}]), - - ?assertMatch({ok, [{app3,{{0,3},{[],[]}}}, - {app2,{{0,3},{[],[]}}}, - {app4,{{0,2},{[],[]}}}, - {app1,{{0,1},{[],[]}}}]}, - X). - -third_test() -> - - Pkg1Deps = [{app2, "0.1.0", '>='}, - {app3, "0.1.1", "0.1.5", between}], - - Pkg2Deps = [{app4, "5.0.0", gte}], - Pkg3Deps = [{app5, "2.0.0", '>='}], - Pkg4Deps = [app5], - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, - {"0.2", Pkg1Deps}, - {"3.0", Pkg1Deps}]}, - {app2, [{"0.0.1", Pkg2Deps}, - {"0.1", Pkg2Deps}, - {"1.0", Pkg2Deps}, - {"3.0", Pkg2Deps}]}, - {app3, [{"0.1.0", Pkg3Deps}, - {"0.1.3", Pkg3Deps}, - {"2.0.0", Pkg3Deps}, - {"3.0.0", Pkg3Deps}, - {"4.0.0", Pkg3Deps}]}, - {app4, [{"0.1.0", Pkg4Deps}, - {"0.3.0", Pkg4Deps}, - {"5.0.0", Pkg4Deps}, - {"6.0.0", Pkg4Deps}]}, - {app5, [{"0.1.0", []}, - {"0.3.0", []}, - {"2.0.0", []}, - {"6.0.0", []}]}]), - - ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{6,0,0},{[],[]}}}, - {app2,{{3,0},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), - - - ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{6,0,0},{[],[]}}}, - {app2,{{3,0},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [app1])). - -fail_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), - [{app1, [{"0.1", [{app2, "0.2"}, - {app3, "0.2", gte}]}, - {"0.2", []}, - {"0.3", []}]}, - {app2, [{"0.1", []}, - {"0.2",[{app3, "0.1"}]}, - {"0.3", []}]}, - {app3, [{"0.1", []}, - {"0.2", []}, - {"0.3", []}]}]), - - Ret = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), - %% We do this to make sure all errors can be formated. - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, - [{[{[{app1,{{0,1},{[],[]}}}], - [{app1,{{0,1},{[],[]}}},[[{app2,{{0,2},{[],[]}}}]]]}], - [{{app2,{{0,2},{[],[]}}},[{app3,{{0,1},{[],[]}}}]}, - {{app1,{{0,1},{[],[]}}},[{app3,{{0,2},{[],[]}},gte}]}]}]}, - Ret). - -conflicting_passing_test() -> - Pkg1Deps = [{app2, "0.1.0", '>='}, - {app5, "2.0.0"}, - {app4, "0.3.0", "5.0.0", between}, - {app3, "0.1.1", "0.1.5", between}], - - Pkg2Deps = [{app4, "3.0.0", gte}], - Pkg3Deps = [{app5, "2.0.0", '>='}], - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, - {"0.1.0", Pkg1Deps}, - {"0.2", Pkg1Deps}, - {"3.0", Pkg1Deps}]}, - {app2, [{"0.0.1", Pkg2Deps}, - {"0.1", Pkg2Deps}, - {"1.0", Pkg2Deps}, - {"3.0", Pkg2Deps}]}, - {app3, [{"0.1.0", Pkg3Deps}, - {"0.1.3", Pkg3Deps}, - {"2.0.0", Pkg3Deps}, - {"3.0.0", Pkg3Deps}, - {"4.0.0", Pkg3Deps}]}, - {app4, [{"0.1.0", [{app5, "0.1.0"}]}, - {"0.3.0", [{app5, "0.3.0"}]}, - {"5.0.0", [{app5, "2.0.0"}]}, - {"6.0.0", [{app5, "6.0.0"}]}]}, - {app5, [{"0.1.0", []}, - {"0.3.0", []}, - {"2.0.0", []}, - {"6.0.0", []}]}]), - - ?assertMatch({ok, [{app5,{{2,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{5,0,0},{[],[]}}}, - {app2,{{3,0},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), - - ?assertMatch({ok, [{app5,{{2,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{5,0,0},{[],[]}}}, - {app2,{{3,0},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [app1, app2, app5])). - - - -circular_dependencies_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", [app2]}]}, - {app2, [{"0.0.1", [app1]}]}]), - - ?assertMatch({ok, [{app1,{{0,1,0},{[],[]}}},{app2,{{0,0,1},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "0.1.0"}])). - -conflicting_failing_test() -> - Pkg1Deps = [app2, - {app5, "2.0.0", '='}, - {app4, "0.3.0", "5.0.0", between}], - - Pkg2Deps = [{app4, "5.0.0", gte}], - Pkg3Deps = [{app5, "6.0.0"}], - - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"3.0", Pkg1Deps}]}, - {app2, [{"0.0.1", Pkg2Deps}]}, - {app3, [{"0.1.0", Pkg3Deps}]}, - {app4, [{"5.0.0", [{app5, "2.0.0"}]}]}, - {app5, [{"2.0.0", []}, - {"6.0.0", []}]}]), - Ret = rcl_depsolver:solve(Dom0, [app1, app3]), - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, - [{[{[app1], - [{app1,{{3,0},{[],[]}}}, - [[{app4,{{5,0,0},{[],[]}}}], - [{app2,{{0,0,1},{[],[]}}},[[{app4,{{5,0,0},{[],[]}}}]]]]]}, - {[app3], - [{app3,{{0,1,0},{[],[]}}},[[{app5,{{6,0,0},{[],[]}}}]]]}], - [{{app4,{{5,0,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}}}]}, - {{app1,{{3,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}},'='}]}]}]}, - Ret). - - -pessimistic_major_minor_patch_test() -> - - Pkg1Deps = [{app2, "2.1.1", '~>'}, - {app3, "0.1.1", "0.1.5", between}], - - Pkg2Deps = [{app4, "5.0.0", gte}], - Pkg3Deps = [{app5, "2.0.0", '>='}], - Pkg4Deps = [app5], - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, - {"0.2", Pkg1Deps}, - {"3.0", Pkg1Deps}]}, - {app2, [{"0.0.1", Pkg2Deps}, - {"0.1", Pkg2Deps}, - {"1.0", Pkg2Deps}, - {"2.1.5", Pkg2Deps}, - {"2.2", Pkg2Deps}, - {"3.0", Pkg2Deps}]}, - {app3, [{"0.1.0", Pkg3Deps}, - {"0.1.3", Pkg3Deps}, - {"2.0.0", Pkg3Deps}, - {"3.0.0", Pkg3Deps}, - {"4.0.0", Pkg3Deps}]}, - {app4, [{"0.1.0", Pkg4Deps}, - {"0.3.0", Pkg4Deps}, - {"5.0.0", Pkg4Deps}, - {"6.0.0", Pkg4Deps}]}, - {app5, [{"0.1.0", []}, - {"0.3.0", []}, - {"2.0.0", []}, - {"6.0.0", []}]}]), - ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{6,0,0},{[],[]}}}, - {app2,{{2,1,5},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). - -pessimistic_major_minor_test() -> - - Pkg1Deps = [{app2, "2.1", '~>'}, - {app3, "0.1.1", "0.1.5", between}], - - Pkg2Deps = [{app4, "5.0.0", gte}], - Pkg3Deps = [{app5, "2.0.0", '>='}], - Pkg4Deps = [app5], - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, - {"0.2", Pkg1Deps}, - {"3.0", Pkg1Deps}]}, - {app2, [{"0.0.1", Pkg2Deps}, - {"0.1", Pkg2Deps}, - {"1.0", Pkg2Deps}, - {"2.1.5", Pkg2Deps}, - {"2.2", Pkg2Deps}, - {"3.0", Pkg2Deps}]}, - {app3, [{"0.1.0", Pkg3Deps}, - {"0.1.3", Pkg3Deps}, - {"2.0.0", Pkg3Deps}, - {"3.0.0", Pkg3Deps}, - {"4.0.0", Pkg3Deps}]}, - {app4, [{"0.1.0", Pkg4Deps}, - {"0.3.0", Pkg4Deps}, - {"5.0.0", Pkg4Deps}, - {"6.0.0", Pkg4Deps}]}, - {app5, [{"0.1.0", []}, - {"0.3.0", []}, - {"2.0.0", []}, - {"6.0.0", []}]}]), - ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, - {app3,{{0,1,3},{[],[]}}}, - {app4,{{6,0,0},{[],[]}}}, - {app2,{{2,2},{[],[]}}}, - {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). - -filter_versions_test() -> - - Cons = [{app2, "2.1", '~>'}, - {app3, "0.1.1", "0.1.5", between}, - {app4, "5.0.0", gte}, - {app5, "2.0.0", '>='}, - app5], - - Packages = [{app1, "0.1.0"}, - {app1, "0.2"}, - {app1, "0.2"}, - {app1, "3.0"}, - {app2, "0.0.1"}, - {app2, "0.1"}, - {app2, "1.0"}, - {app2, "2.1.5"}, - {app2, "2.2"}, - {app2, "3.0"}, - {app3, "0.1.0"}, - {app3, "0.1.3"}, - {app3, "2.0.0"}, - {app3, "3.0.0"}, - {app3, "4.0.0"}, - {app4, "0.1.0"}, - {app4, "0.3.0"}, - {app4, "5.0.0"}, - {app4, "6.0.0"}, - {app5, "0.1.0"}, - {app5, "0.3.0"}, - {app5, "2.0.0"}, - {app5, "6.0.0"}], - - ?assertMatch({ok, [{app1,"0.1.0"}, - {app1,"0.2"}, - {app1,"0.2"}, - {app1,"3.0"}, - {app2,"2.1.5"}, - {app2,"2.2"}, - {app3,"0.1.3"}, - {app4,"5.0.0"}, - {app4,"6.0.0"}, - {app5,"2.0.0"}, - {app5,"6.0.0"}]}, - rcl_depsolver:filter_packages(Packages, Cons)), - - Ret = rcl_depsolver:filter_packages(Packages, - [{"foo", "1.0.0", '~~~~'} | Cons]), - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, {invalid_constraints, [{<<"foo">>,{{1,0,0},{[],[]}},'~~~~'}]}}, Ret). - - --spec missing_test() -> ok. -missing_test() -> - - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, - {app3, "0.2", '>='}, - {app4, "0.2", '='}]}, - {"0.2", [{app4, "0.2"}]}, - {"0.3", [{app4, "0.2", '='}]}]}, - {app2, [{"0.1", []}, - {"0.2",[{app3, "0.3"}]}, - {"0.3", []}]}, - {app3, [{"0.1", []}, - {"0.2", []}, - {"0.3", []}]}]), - Ret1 = rcl_depsolver:solve(Dom0, [{app4, "0.1"}, {app3, "0.1"}]), - _ = rcl_depsolver:format_error(Ret1), - ?assertMatch({error,{unreachable_package,app4}}, Ret1), - - Ret2 = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), - _ = rcl_depsolver:format_error(Ret2), - ?assertMatch({error,{unreachable_package,app4}}, - Ret2). - - -binary_test() -> - - World = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, - {<<"bar">>, [{<<"2.0.0">>, [{<<"foo">>, <<"3.0.0">>, gt}]}]}], - Ret = rcl_depsolver:solve(rcl_depsolver:add_packages(rcl_depsolver:new_graph(), - World), - [<<"foo">>]), - - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, - [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], - [{{<<"foo">>,{{1,2,3},{[],[]}}}, - [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). - -%% -%% We don't have bar cookbook -%% -%% Ruby gives -%% "message":"Unable to satisfy constraints on cookbook bar, which does not -%% exist, due to run list item (foo >= 0.0.0). Run list items that may result -%% in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", -%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", -%% "non_existent_cookbooks":["bar"]," -%% "most_constrained_cookbooks":[]}" -%% -doesnt_exist_test() -> - Constraints = [{<<"foo">>,[{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}], - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error,{unreachable_package,<<"bar">>}}, Ret). - -%% -%% We have v 2.0.0 of bar but want > 2.0.0 -%% -%% Ruby gives -%% "message":"Unable to satisfy constraints on cookbook bar due to run list item -%% (foo >= 0.0.0). Run list items that may result in a constraint on bar: [(foo -%% = 1.2.3) -> (bar > 2.0.0)]", -%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", -%% "non_existent_cookbooks":[], -%% "most_constrained_cookbooks":["bar 2.0.0 -> []"] -%% -not_new_enough_test() -> - - Constraints = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, - {<<"bar">>, [{<<"2.0.0">>, []}]}], - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, - [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], - [{{<<"foo">>,{{1,2,3},{[],[]}}}, - [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). - -%% -%% circular deps are bad -%% -%% Ruby gives -%% "message":"Unable to satisfy constraints on cookbook bar due to run list item (foo >= 0.0.0). -%% Run list items that may result in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", -%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", -%% "non_existent_cookbooks":[], -%% "most_constrained_cookbooks:["bar = 2.0.0 -> [(foo > 3.0.0)]"] -%% -impossible_dependency_test() -> - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), - [{<<"foo">>, [{<<"1.2.3">>,[{ <<"bar">>, <<"2.0.0">>, gt}]}]}, - {<<"bar">>, [{<<"2.0.0">>, [{ <<"foo">>, <<"3.0.0">>, gt}]}]}]), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), - ?assertMatch({error, - [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], - [{{<<"foo">>,{{1,2,3},{[],[]}}}, - [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). - -%% -%% Formatting tests -%% -format_test_() -> - [{"format constraint", - [equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(<<"foo">>)), - equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(foo)), - equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}})), - equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '='})), - equal_bin_string(<<"(foo > 1.2.0)">>, - rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '>'})), - equal_bin_string(<<"(foo > 1.2.0)">>, - rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, gt})), - equal_bin_string(<<"(foo between 1.2.0 and 1.3.0)">>, - rcl_depsolver:format_constraint({<<"foo">>,{{1,2,0}, {[], []}}, - {{1,3,0}, {[], []}}, between})), - equal_bin_string(<<"(foo > 1.2.0-alpha.1+build.36)">>, - rcl_depsolver:format_constraint({<<"foo">>, - {{1,2,0}, {["alpha", 1], ["build", 36]}}, gt})) - ] - }, - {"format roots", - [equal_bin_string(<<"(bar = 1.2.0)">>, - rcl_depsolver:format_roots([ [{<<"bar">>, {{1,2,0},{[],[]}}}] ])), - equal_bin_string(<<"(bar = 1.2.0), foo">>, - rcl_depsolver:format_roots([[<<"foo">>, - {<<"bar">>, {{1,2,0},{[],[]}}}]])), - equal_bin_string(<<"(bar = 1.2.0), foo">>, - rcl_depsolver:format_roots([[<<"foo">>], [{<<"bar">>, {{1,2,0},{[],[]}}}]])) - ] - } - ]. - -%% -%% Internal functions -%% -equal_bin_string(Expected, Got) -> - ?_assertEqual(Expected, erlang:iolist_to_binary(Got)). diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl deleted file mode 100644 index 05da548..0000000 --- a/test/rclt_command_SUITE.erl +++ /dev/null @@ -1,97 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Eric Merrit -%%% @copyright (C) 2012, Eric Merrit --module(rclt_command_SUITE). - --export([suite/0, - init_per_suite/1, - end_per_suite/1, - all/0, - normal_passing_case/1, - lib_fail_case/1, - spec_parse_fail_case/1, - config_fail_case/1]). - --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). - -suite() -> - [{timetrap,{seconds,30}}]. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -all() -> - [normal_passing_case, lib_fail_case, config_fail_case]. - -normal_passing_case(Config) -> - DataDir = proplists:get_value(data_dir, Config), - Lib1 = filename:join([DataDir, <<"lib1">>]), - Lib2 = filename:join([DataDir, <<"lib2">>]), - Outdir = filename:join([DataDir, "outdir"]), - ok = rcl_util:mkdir_p(Lib1), - ok = rcl_util:mkdir_p(Lib2), - Goal1 = "app1<=33.33+build4", - Goal2 = "app2:btwn:33.22,45.22+build.21", - - LogLevel = "2", - RelName = "foo-release", - RelVsn = "33.222", - CmdLine = ["-V", LogLevel, "-g",Goal1,"-g",Goal2, "-l", Lib1, "-l", Lib2, - "-n", RelName, "-v", RelVsn, "-o", Outdir], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), - {ok, State} = rcl_cmd_args:args2state(Opts, Targets), - ?assertMatch([Lib1, Lib2], - rcl_state:lib_dirs(State)), - ?assertMatch(Outdir, rcl_state:output_dir(State)), - - ?assertMatch([{app1,{{33,33},{[],[<<"build4">>]}},lte}, - {app2, - {{33,22},{[],[]}}, - {{45,22},{[],[<<"build">>,21]}}, between}], - rcl_state:goals(State)). - - -lib_fail_case(Config) -> - DataDir = proplists:get_value(data_dir, Config), - Lib1 = filename:join([DataDir, "lib1"]), - Lib2 = filename:join([DataDir, "lib3333"]), - ok = rcl_util:mkdir_p(Lib1), - - CmdLine = ["-l", Lib1, "-l", Lib2], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), - ?assertMatch({error, {_, {not_directory, Lib2}}}, - rcl_cmd_args:args2state(Opts, Targets)). - -spec_parse_fail_case(_Config) -> - Spec = "aaeu:3333:33.22a44", - CmdLine = ["-g", Spec], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), - ?assertMatch({error, {_, {failed_to_parse, _Spec}}}, - rcl_cmd_args:args2state(Opts, Targets)). - -config_fail_case(_Config) -> - ConfigFile = "does-not-exist", - CmdLine = ["-c", ConfigFile], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), - ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, - rcl_cmd_args:args2state(Opts, Targets)). diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl deleted file mode 100644 index e3a2861..0000000 --- a/test/rclt_discover_SUITE.erl +++ /dev/null @@ -1,193 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Eric Merrit -%%% @copyright (C) 2012, Eric Merrit --module(rclt_discover_SUITE). - --export([suite/0, - init_per_suite/1, - end_per_suite/1, - init_per_testcase/2, - all/0, - normal_case/1, - no_beam_case/1, - bad_ebin_case/1]). - --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). - -suite() -> - [{timetrap,{seconds,30}}]. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_testcase(_, Config) -> - DataDir = proplists:get_value(data_dir, Config), - LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), - LibDir2 = filename:join([DataDir, create_random_name("lib_dir2_")]), - ok = rcl_util:mkdir_p(LibDir1), - ok = rcl_util:mkdir_p(LibDir2), - State = rcl_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), - [{lib1, LibDir1}, - {lib2, LibDir2}, - {state, State} | Config]. - - -all() -> - [normal_case, no_beam_case, bad_ebin_case]. - -normal_case(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - Apps1 = [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - LibDir2 = proplists:get_value(lib2, Config), - Apps2 = [(fun({Name, Vsn}) -> - create_app(LibDir2, Name, Vsn) - end)(App) - || App <- - [{create_random_name("lib_app2_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - State0 = rcl_state:put(proplists:get_value(state, Config), - disable_default_libs, true), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), - {ok, State2} = rcl_provider:do(DiscoverProvider, State1), - lists:foreach(fun(App) -> - ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) - end, Apps1), - - lists:foreach(fun(App) -> - ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) - end, Apps2), - Length = erlang:length(Apps2) + - erlang:length(Apps2), - ?assertMatch(Length, erlang:length(rcl_state:available_apps(State2))). - -no_beam_case(Config) -> - %% We silently ignore apps with no beams - LibDir1 = proplists:get_value(lib1, Config), - _Apps1 = [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - LibDir2 = proplists:get_value(lib2, Config), - _Apps2 = [(fun({Name, Vsn}) -> - create_app(LibDir2, Name, Vsn) - end)(App) - || App <- - [{create_random_name("lib_app2_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - BadName = create_random_name("error_bad"), - BadVsn = create_random_vsn(), - AppDir = filename:join([LibDir2, BadName]), - write_app_file(AppDir, BadName, BadVsn), - State0 = proplists:get_value(state, Config), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), - EbinDir = filename:join([LibDir2, BadName, <<"ebin">>]), - ?assertMatch({error, {_, [{no_beam_files, EbinDir}]}}, - rcl_provider:do(DiscoverProvider, State1)). - -bad_ebin_case(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - _Apps1 = [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - LibDir2 = proplists:get_value(lib2, Config), - _Apps2 = [(fun({Name, Vsn}) -> - create_app(LibDir2, Name, Vsn) - end)(App) - || App <- - [{create_random_name("lib_app2_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - BadName = create_random_name("error_bad"), - BadVsn = create_random_vsn(), - AppDir = filename:join([LibDir2, BadName]), - Filename = filename:join([AppDir, <<"ebin">>, BadName ++ ".app"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, get_bad_app_metadata(BadName, BadVsn)), - write_beam_file(AppDir, BadName), - State0 = proplists:get_value(state, Config), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), - ?assertMatch({error, {_, [{invalid_app_file, Filename}]}}, - rcl_provider:do(DiscoverProvider, State1)). - - -%%%=================================================================== -%%% Helper functions -%%%=================================================================== -create_app(Dir, Name, Vsn) -> - AppDir = filename:join([Dir, Name]), - write_app_file(AppDir, Name, Vsn), - write_beam_file(AppDir, Name), - {ok, App} = rcl_app_info:new(erlang:list_to_atom(Name), Vsn, - erlang:iolist_to_binary(AppDir), - [kernel, stdlib], []), - App. - -write_beam_file(Dir, Name) -> - Beam = filename:join([Dir, "ebin", "not_a_real_beam" ++ Name ++ ".beam"]), - ok = filelib:ensure_dir(Beam), - ok = ec_file:write_term(Beam, testing_purposes_only). - -write_app_file(Dir, Name, Version) -> - Filename = filename:join([Dir, "ebin", Name ++ ".app"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, get_app_metadata(Name, Version)). - -get_app_metadata(Name, Vsn) -> - {application, erlang:list_to_atom(Name), - [{description, ""}, - {vsn, Vsn}, - {modules, []}, - {applications, [kernel, stdlib]}]}. - -get_bad_app_metadata(Name, Vsn) -> - ["{application, ", Name, ", - [{description, \"\"}, - {vsn, \"", Vsn, "\"}, - {modules, [], - {applications, [kernel, stdlib]}]}."]. - - -create_random_name(Name) -> - random:seed(erlang:now()), - Name ++ erlang:integer_to_list(random:uniform(1000000)). - -create_random_vsn() -> - random:seed(erlang:now()), - lists:flatten([erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100))]). diff --git a/test/rclt_goal.erl b/test/rclt_goal.erl deleted file mode 100644 index 20fb5e5..0000000 --- a/test/rclt_goal.erl +++ /dev/null @@ -1,63 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%--------------------------------------------------------------------------- -%%% @author Eric Merritt -%%% @copyright (C) 2012 Erlware, LLC. -%%% @doc test for target spec parsing --module(rclt_goal). - --include_lib("eunit/include/eunit.hrl"). - -parse_test() -> - ?assertMatch({ok, getopt}, - rcl_goal:parse("getopt")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt=0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt:0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt-0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, - rcl_goal:parse("getopt >= 0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, - rcl_goal:parse("getopt:gte:0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, - rcl_goal:parse("getopt>0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, - rcl_goal:parse("getopt:gt:0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, - rcl_goal:parse("getopt<= 0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, - rcl_goal:parse("getopt:lte:0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lt}}, - rcl_goal:parse("getopt<0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, - rcl_goal:parse("getopt ~>0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, - rcl_goal:parse("getopt: pes:0.5.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, - rcl_goal:parse("getopt:btwn:0.5.1,0.6.1")), - ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, - rcl_goal:parse("getopt:between :0.5.1,0.6.1")). - -fail_test() -> - ?assertMatch({fail,_}, - rcl_goal:parse("got:")), - ?assertMatch({fail,_}, - rcl_goal:parse("between:btwn:0.5")), - ?assertMatch({fail,_}, - rcl_goal:parse("between:btwn:0.5,")). diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl deleted file mode 100644 index 90d99c9..0000000 --- a/test/rclt_release_SUITE.erl +++ /dev/null @@ -1,837 +0,0 @@ -%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- -%%% Copyright 2012 Erlware, LLC. All Rights Reserved. -%%% -%%% This file is provided to you under the Apache License, -%%% Version 2.0 (the "License"); you may not use this file -%%% except in compliance with the License. You may obtain -%%% a copy of the License at -%%% -%%% http://www.apache.org/licenses/LICENSE-2.0 -%%% -%%% Unless required by applicable law or agreed to in writing, -%%% software distributed under the License is distributed on an -%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%%% KIND, either express or implied. See the License for the -%%% specific language governing permissions and limitations -%%% under the License. -%%%------------------------------------------------------------------- -%%% @author Eric Merrit -%%% @copyright (C) 2012, Eric Merrit --module(rclt_release_SUITE). - --export([suite/0, - init_per_suite/1, - end_per_suite/1, - init_per_testcase/2, - all/0, - make_release/1, - make_scriptless_release/1, - make_overridden_release/1, - make_skip_app_release/1, - make_rerun_overridden_release/1, - make_implicit_config_release/1, - overlay_release/1, - make_goalless_release/1, - make_depfree_release/1, - make_invalid_config_release/1, - make_relup_release/1, - make_relup_release2/1, - make_one_app_top_level_release/1]). - --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("kernel/include/file.hrl"). --include_lib("kernel/include/file.hrl"). - -suite() -> - [{timetrap,{seconds,30}}]. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_testcase(_, Config) -> - DataDir = proplists:get_value(data_dir, Config), - LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), - ok = rcl_util:mkdir_p(LibDir1), - State = rcl_state:new([{lib_dirs, [LibDir1]}], release), - [{lib1, LibDir1}, - {state, State} | Config]. - -all() -> - [make_release, make_scriptless_release, make_overridden_release, - make_skip_app_release, - make_implicit_config_release, make_rerun_overridden_release, - overlay_release, make_goalless_release, make_depfree_release, - make_invalid_config_release, make_relup_release, make_relup_release2, - make_one_app_top_level_release]. - -make_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1, - goal_app_2]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - -make_invalid_config_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - ok = ec_file:write(ConfigFile, - "{release, {foo, \"0.0.1\"}, - [goal_app_1, - goal_app_2,]}"), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {error, {rcl_prv_config, - {consult, _, _}}} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile). - -make_scriptless_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{generate_start_script, false}, - {release, {foo, "0.0.1"}, - [goal_app_1, - goal_app_2]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile), - - ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo"]))), - ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo-0.0.1"]))), - - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - - -make_overridden_release(Config) -> - DataDir = proplists:get_value(data_dir, Config), - OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - OverrideApp = create_random_name("override_app"), - OverrideVsn = create_random_vsn(), - OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" ++ OverrideVsn), - OverrideAppName = erlang:list_to_atom(OverrideApp), - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1, - erlang:list_to_atom(OverrideApp), - goal_app_2]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, Cwd} = file:get_cwd(), - {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, - OutputDir, [{OverrideAppName, OverrideAppDir}], - ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({OverrideAppName, OverrideVsn}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), - {ok, Real} = file:read_link(filename:join([OutputDir, "lib", - OverrideApp ++ "-" ++ OverrideVsn])), - ?assertMatch(OverrideAppDir, Real). - -make_skip_app_release(Config) -> - DataDir = proplists:get_value(data_dir, Config), - SkipAppDir1 = filename:join([DataDir, create_random_name("skip_app_dir_")]), - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - SkipAppApp = create_random_name("skip_app_app"), - SkipAppVsn = create_random_vsn(), - SkipAppAppName = erlang:list_to_atom(SkipAppApp), - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - create_empty_app(SkipAppDir1, SkipAppApp, SkipAppVsn, [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1, - goal_app_2]}, - {skip_apps, [erlang:list_to_atom(SkipAppApp)]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, Cwd} = file:get_cwd(), - {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, - OutputDir, [], - ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assertNot(lists:member({SkipAppAppName, SkipAppVsn}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - -make_implicit_config_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - FooRoot = filename:join([LibDir1, "foodir1", "foodir2"]), - filelib:ensure_dir(filename:join([FooRoot, "tmp"])), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1, - goal_app_2]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - ok = file:set_cwd(FooRoot), - {ok, FooRoot} = file:get_cwd(), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, undefined), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - ?assert(ec_file:exists(OutputDir)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - -make_rerun_overridden_release(Config) -> - DataDir = proplists:get_value(data_dir, Config), - OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - OverrideApp = create_random_name("override_app"), - OverrideVsn = create_random_vsn(), - OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" - ++ OverrideVsn), - OverrideAppName = erlang:list_to_atom(OverrideApp), - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1, - erlang:list_to_atom(OverrideApp), - goal_app_2]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, Cwd} = file:get_cwd(), - {ok, _} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, - OutputDir, [{OverrideAppName, OverrideAppDir}], - ConfigFile), - - %% Now we run it again to see if it fails. - {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2, - OutputDir, [{OverrideAppName, OverrideAppDir}], - ConfigFile), - - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({OverrideAppName, OverrideVsn}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), - {ok, Real} = file:read_link(filename:join([OutputDir, "lib", - OverrideApp ++ "-" ++ OverrideVsn])), - ?assertMatch(OverrideAppDir, Real). - -overlay_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - OverlayVars = filename:join([LibDir1, "vars.config"]), - Template = filename:join([LibDir1, "test_template"]), - write_config(ConfigFile, - [{overlay_vars, OverlayVars}, - {overlay, [{mkdir, "{{target_dir}}/fooo"}, - {copy, OverlayVars, - "{{target_dir}}/{{foo_dir}}/vars.config"}, - {copy, OverlayVars, - "{{target_dir}}/{{yahoo}}/"}, - {template, Template, - "{{target_dir}}/test_template_resolved"}]}, - {release, {foo, "0.0.1"}, - [goal_app_1, - goal_app_2]}]), - - VarsFile = filename:join([LibDir1, "vars.config"]), - write_config(VarsFile, [{yahoo, "yahoo"}, - {yahoo2, [{foo, "bar"}]}, - {yahoo3, [{bar, "{{yahoo}}/{{yahoo2.foo}}"}]}, - {foo_dir, "foodir"}]), - - TemplateFile = filename:join([LibDir1, "test_template"]), - ok = file:write_file(TemplateFile, test_template_contents()), - {ok, FileInfo} = file:read_file_info(TemplateFile), - ok = file:write_file_info(TemplateFile, FileInfo#file_info{mode=8#00777}), - - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile), - - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), - - ?assert(ec_file:exists(filename:join(OutputDir, "fooo"))), - ?assert(ec_file:exists(filename:join([OutputDir, "foodir", "vars.config"]))), - ?assert(ec_file:exists(filename:join([OutputDir, "yahoo", "vars.config"]))), - - TemplateData = case file:consult(filename:join([OutputDir, "test_template_resolved"])) of - {ok, Details} -> - Details; - Error -> - erlang:throw({failed_to_consult, Error}) - end, - {ok, ReadFileInfo} = file:read_file_info(filename:join([OutputDir, "test_template_resolved"])), - ?assertEqual(8#100777, ReadFileInfo#file_info.mode), - - ?assertEqual(erlang:system_info(version), - proplists:get_value(erts_vsn, TemplateData)), - ?assertEqual(erlang:system_info(version), - proplists:get_value(release_erts_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(release_version, TemplateData)), - ?assertEqual(foo, - proplists:get_value(release_name, TemplateData)), - ?assertEqual([kernel,stdlib,lib_dep_1,non_goal_2,non_goal_1, - goal_app_1,goal_app_2], - proplists:get_value(release_applications, TemplateData)), - ?assert(proplists:is_defined(std_version, TemplateData)), - ?assert(proplists:is_defined(kernel_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(non_goal_1_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(non_goal_2_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(goal_app_1_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(goal_app_2_version, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(lib_dep_1, TemplateData)), - ?assert(proplists:is_defined(lib_dep_1_dir, TemplateData)), - ?assertEqual([stdlib,kernel], - proplists:get_value(lib_dep_1_active, TemplateData)), - ?assertEqual([], - proplists:get_value(lib_dep_1_library, TemplateData)), - ?assertEqual("false", - proplists:get_value(lib_dep_1_link, TemplateData)), - ?assertEqual("(2:debug)", - proplists:get_value(log, TemplateData)), - ?assertEqual(OutputDir, - proplists:get_value(output_dir, TemplateData)), - ?assertEqual(OutputDir, - proplists:get_value(target_dir, TemplateData)), - ?assertEqual([], - proplists:get_value(overridden, TemplateData)), - ?assertEqual([""], - proplists:get_value(goals, TemplateData)), - ?assert(proplists:is_defined(lib_dirs, TemplateData)), - ?assert(proplists:is_defined(config_file, TemplateData)), - ?assertEqual([""], - proplists:get_value(goals, TemplateData)), - ?assertEqual([], - proplists:get_value(sys_config, TemplateData)), - ?assert(proplists:is_defined(root_dir, TemplateData)), - ?assertEqual(foo, - proplists:get_value(default_release_name, TemplateData)), - ?assertEqual("0.0.1", - proplists:get_value(default_release_version, TemplateData)), - ?assertEqual("foo-0.0.1", - proplists:get_value(default_release, TemplateData)), - ?assertEqual("yahoo", - proplists:get_value(yahoo, TemplateData)), - ?assertEqual("bar", - proplists:get_value(yahoo2_foo, TemplateData)), - ?assertEqual("foodir", - proplists:get_value(foo_dir, TemplateData)), - ?assertEqual("yahoo/bar", - proplists:get_value(yahoo3, TemplateData)). - -make_goalless_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - []}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - ?assertMatch({error,{rcl_prv_release,no_goals_specified}}, - relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile)). - -make_depfree_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "non_goal_2", "0.0.1", [kernel,stdlib], []), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [goal_app_1]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)). - -make_relup_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), - {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), - {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - write_appup_file(GA1, "0.0.2"), - write_appup_file(GA2, "0.0.2"), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [sasl, - {goal_app_1, "0.0.1"}, - {goal_app_2, "0.0.1"}]}, - {release, {foo, "0.0.2"}, - [sasl, - {goal_app_1, "0.0.2"}, - {goal_app_2, "0.0.2"}]}, - {release, {foo, "0.0.3"}, - [sasl, - {goal_app_1, "0.0.3"}, - {goal_app_2, "0.0.3"}]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, - OutputDir, ConfigFile), - {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, - OutputDir, ConfigFile), - {ok, State} = relcool:do([{relname, foo}, - {relvsn, "0.0.3"}, - {goals, []}, - {lib_dirs, [LibDir1]}, - {log_level, 2}, - {output_dir, OutputDir}, - {config, ConfigFile}], ["relup"]), - - %% we should have one 'resolved' release and three discovered realized_releases. - ?assertMatch([{foo, "0.0.1"}, - {foo, "0.0.2"}, - {foo, "0.0.3"}], - lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), - Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), - ?assert(rcl_release:realized(Release)), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, - rcl_state:realized_releases(State)))), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, - rcl_state:realized_releases(State)))), - - ?assertMatch({ok, [{"0.0.3", - [{"0.0.2",[],[point_of_no_return]}], - [{"0.0.2",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), - filename:basename(rcl_release:relfile(Release), ".rel") ++ - ".relup"))), - - ?assertMatch(foo, rcl_release:name(Release)), - ?assertMatch("0.0.3", rcl_release:vsn(Release)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.3"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.3"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - - -make_relup_release2(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), - {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), - {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - write_appup_file(GA1, "0.0.1"), - write_appup_file(GA2, "0.0.1"), - - ConfigFile = filename:join([LibDir1, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [sasl, - {goal_app_1, "0.0.1"}, - {goal_app_2, "0.0.1"}]}, - {release, {foo, "0.0.2"}, - [sasl, - {goal_app_1, "0.0.2"}, - {goal_app_2, "0.0.2"}]}, - {release, {foo, "0.0.3"}, - [sasl, - {goal_app_1, "0.0.3"}, - {goal_app_2, "0.0.3"}]}]), - OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, - OutputDir, ConfigFile), - {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, - OutputDir, ConfigFile), - {ok, State} = relcool:do([{relname, foo}, - {relvsn, "0.0.3"}, - {upfrom, "0.0.1"}, - {goals, []}, - {lib_dirs, [LibDir1]}, - {log_level, 2}, - {output_dir, OutputDir}, - {config, ConfigFile}], ["relup"]), - - %% we should have one 'resolved' release and three discovered realized_releases. - ?assertMatch([{foo, "0.0.1"}, - {foo, "0.0.2"}, - {foo, "0.0.3"}], - lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), - Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), - ?assert(rcl_release:realized(Release)), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, - rcl_state:realized_releases(State)))), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, - rcl_state:realized_releases(State)))), - - ?assertMatch({ok, [{"0.0.3", - [{"0.0.1",[],[point_of_no_return]}], - [{"0.0.1",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), - filename:basename(rcl_release:relfile(Release), ".rel") ++ - ".relup"))), - - ?assertMatch(foo, rcl_release:name(Release)), - ?assertMatch("0.0.3", rcl_release:vsn(Release)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), - ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.3"}, AppSpecs)), - ?assert(lists:member({goal_app_2, "0.0.3"}, AppSpecs)), - ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). - - -make_one_app_top_level_release(Config) -> - LibDir1 = proplists:get_value(lib1, Config), - {ok, AppInfo} = create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel], []), - AppDir = rcl_app_info:dir(AppInfo), - ConfigFile = filename:join([AppDir, "relcool.config"]), - write_config(ConfigFile, - [{release, {foo, "0.0.1"}, - [{goal_app_1, "0.0.1"}]}]), - - OutputDir = filename:join([AppDir, - create_random_name("relcool-output")]), - - {ok, Cwd} = file:get_cwd(), - ok = file:set_cwd(AppDir), - {ok, State} = relcool:do(undefined, undefined, [], [], 2, - OutputDir, ConfigFile), - ok = file:set_cwd(Cwd), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), - ?assert(lists:keymember(stdlib, 1, AppSpecs)), - ?assert(lists:keymember(kernel, 1, AppSpecs)), - ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)). - - -%%%=================================================================== -%%% Helper Functions -%%%=================================================================== - -create_app(Dir, Name, Vsn, Deps, LibDeps) -> - AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), - write_app_file(AppDir, Name, Vsn, Deps, LibDeps), - write_beam_file(AppDir, Name), - rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, - Deps, []). - -create_empty_app(Dir, Name, Vsn, Deps, LibDeps) -> - AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), - write_app_file(AppDir, Name, Vsn, Deps, LibDeps), - rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, - Deps, []). - -write_beam_file(Dir, Name) -> - Beam = filename:join([Dir, "ebin", "not_a_real_beam" ++ Name ++ ".beam"]), - ok = filelib:ensure_dir(Beam), - ok = ec_file:write_term(Beam, testing_purposes_only). - -write_appup_file(AppInfo, DownVsn) -> - Dir = rcl_app_info:dir(AppInfo), - Name = rcl_util:to_string(rcl_app_info:name(AppInfo)), - Vsn = rcl_app_info:vsn_as_string(AppInfo), - Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). - -write_app_file(Dir, Name, Version, Deps, LibDeps) -> - Filename = filename:join([Dir, "ebin", Name ++ ".app"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, get_app_metadata(Name, Version, Deps, LibDeps)). - -get_app_metadata(Name, Vsn, Deps, LibDeps) -> - {application, erlang:list_to_atom(Name), - [{description, ""}, - {vsn, Vsn}, - {modules, []}, - {included_applications, LibDeps}, - {registered, []}, - {applications, Deps}]}. - -create_random_name(Name) -> - random:seed(erlang:now()), - Name ++ erlang:integer_to_list(random:uniform(1000000)). - -create_random_vsn() -> - random:seed(erlang:now()), - lists:flatten([erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100))]). - -write_config(Filename, Values) -> - ok = ec_file:write(Filename, - [io_lib:format("~p.\n", [Val]) || Val <- Values]). - -test_template_contents() -> - "{erts_vsn, \"{{erts_vsn}}\"}.\n" - "{release_erts_version, \"{{release_erts_version}}\"}.\n" - "{release_name, {{release_name}}}.\n" - "{rel_vsn, \"{{release_version}}\"}.\n" - "{release_version, \"{{release_version}}\"}.\n" - "{release_applications, [{{ release_applications|join:\", \" }}]}.\n" - "{std_version, \"{{release.stdlib.version}}\"}.\n" - "{kernel_version, \"{{release.kernel.version}}\"}.\n" - "{non_goal_1_version, \"{{release.non_goal_1.version}}\"}.\n" - "{non_goal_2_version, \"{{release.non_goal_2.version}}\"}.\n" - "{goal_app_1_version, \"{{release.goal_app_1.version}}\"}.\n" - "{goal_app_2_version, \"{{release.goal_app_2.version}}\"}.\n" - "{lib_dep_1, \"{{release.lib_dep_1.version}}\"}.\n" - "{lib_dep_1_dir, \"{{release.lib_dep_1.dir}}\"}.\n" - "{lib_dep_1_active, [{{ release.lib_dep_1.active_dependencies|join:\", \" }}]}.\n" - "{lib_dep_1_library, [{{ release.lib_dep_1.library_dependencies|join:\", \" }}]}.\n" - "{lib_dep_1_link, \"{{release.lib_dep_1.link}}\"}.\n" - "{log, \"{{log}}\"}.\n" - "{output_dir, \"{{output_dir}}\"}.\n" - "{target_dir, \"{{target_dir}}\"}.\n" - "{overridden, [{{ overridden|join:\", \" }}]}.\n" - "{goals, [\"{{ goals|join:\", \" }}\"]}.\n" - "{lib_dirs, [\"{{ lib_dirs|join:\", \" }}\"]}.\n" - "{config_file, \"{{ config_file }}\"}.\n" - "{providers, [{{ providers|join:\", \" }}]}.\n" - "{sys_config, \"{{sys_config}}\"}.\n" - "{root_dir, \"{{root_dir}}\"}.\n" - "{default_release_name, {{default_release_name}}}.\n" - "{default_release_version, \"{{default_release_version}}\"}.\n" - "{default_release, \"{{default_release}}\"}.\n" - "{yahoo, \"{{yahoo}}\"}.\n" - "{yahoo2_foo, \"{{yahoo2.foo}}\"}.\n" - "{foo_dir, \"{{foo_dir}}\"}.\n" - "{yahoo3, \"{{yahoo3.bar}}\"}.\n". diff --git a/test/rlx_command_SUITE.erl b/test/rlx_command_SUITE.erl new file mode 100644 index 0000000..05da548 --- /dev/null +++ b/test/rlx_command_SUITE.erl @@ -0,0 +1,97 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Eric Merrit +%%% @copyright (C) 2012, Eric Merrit +-module(rclt_command_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + all/0, + normal_passing_case/1, + lib_fail_case/1, + spec_parse_fail_case/1, + config_fail_case/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +all() -> + [normal_passing_case, lib_fail_case, config_fail_case]. + +normal_passing_case(Config) -> + DataDir = proplists:get_value(data_dir, Config), + Lib1 = filename:join([DataDir, <<"lib1">>]), + Lib2 = filename:join([DataDir, <<"lib2">>]), + Outdir = filename:join([DataDir, "outdir"]), + ok = rcl_util:mkdir_p(Lib1), + ok = rcl_util:mkdir_p(Lib2), + Goal1 = "app1<=33.33+build4", + Goal2 = "app2:btwn:33.22,45.22+build.21", + + LogLevel = "2", + RelName = "foo-release", + RelVsn = "33.222", + CmdLine = ["-V", LogLevel, "-g",Goal1,"-g",Goal2, "-l", Lib1, "-l", Lib2, + "-n", RelName, "-v", RelVsn, "-o", Outdir], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, State} = rcl_cmd_args:args2state(Opts, Targets), + ?assertMatch([Lib1, Lib2], + rcl_state:lib_dirs(State)), + ?assertMatch(Outdir, rcl_state:output_dir(State)), + + ?assertMatch([{app1,{{33,33},{[],[<<"build4">>]}},lte}, + {app2, + {{33,22},{[],[]}}, + {{45,22},{[],[<<"build">>,21]}}, between}], + rcl_state:goals(State)). + + +lib_fail_case(Config) -> + DataDir = proplists:get_value(data_dir, Config), + Lib1 = filename:join([DataDir, "lib1"]), + Lib2 = filename:join([DataDir, "lib3333"]), + ok = rcl_util:mkdir_p(Lib1), + + CmdLine = ["-l", Lib1, "-l", Lib2], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + ?assertMatch({error, {_, {not_directory, Lib2}}}, + rcl_cmd_args:args2state(Opts, Targets)). + +spec_parse_fail_case(_Config) -> + Spec = "aaeu:3333:33.22a44", + CmdLine = ["-g", Spec], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + ?assertMatch({error, {_, {failed_to_parse, _Spec}}}, + rcl_cmd_args:args2state(Opts, Targets)). + +config_fail_case(_Config) -> + ConfigFile = "does-not-exist", + CmdLine = ["-c", ConfigFile], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, + rcl_cmd_args:args2state(Opts, Targets)). diff --git a/test/rlx_depsolver_tester.erl b/test/rlx_depsolver_tester.erl new file mode 100644 index 0000000..53f5ac0 --- /dev/null +++ b/test/rlx_depsolver_tester.erl @@ -0,0 +1,474 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- +%% ex: ts=4 sx=4 et +%%------------------------------------------------------------------- +%% +%% Copyright 2012 Opscode, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% @author Eric Merritt +%% @doc +%% Additional testing for depsolver +%% @end +%%------------------------------------------------------------------- +-module(rcl_depsolver_tester). + +-export([run_data/1, run_log/1]). +-include_lib("eunit/include/eunit.hrl"). + +-define(ADD_PKG, "^DepSelector\\sinst#\\s(\\d+)\\s-\\s" + "Adding\\spackage\\sid\\s(\\d+)\\/(\\d+):\\smin\\s=\\s-1," + "\\smax\\s=\\s(\\d+),\\scurrent\\sversion\\s0$"). +-define(ADD_VC, "^DepSelector\\sinst#\\s(\\d+)\\s-\\sAdding\\sVC\\s" + "for\\s(\\d+)\\s@\\s(\\d+)\\sdepPkg\\s(\\d+)\\s\\[\\s(\\d+)" + "\\s(\\d+)\\s\\]$"). +-define(ADD_GOAL, "^DepSelector\\sinst#\\s(\\d+)\\s-\\s" + "Marking\\sPackage\\sRequired\\s(\\d+)$"). + +%%============================================================================ +%% Public Api +%%============================================================================ +run_data(FileName) -> + {ok, Device} = file:open(FileName, [read]), + run_data_file(Device). + +run_log(FileName) -> + {ok, Device} = file:open(FileName, [read]), + run_log_file(Device). + +data1_test() -> + ExpectedResult = versionify([{"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}]), + ?assertMatch({ok, ExpectedResult}, + run_data(fix_rebar_brokenness("data1.txt"))). + +data2_test() -> + ExpectedResult = versionify([{"app18","0.0.1"}, + {"app4","0.0.1"}, + {"app1","0.0.1"}, + {"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg5","0.0.2"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}, + {"dep_pkg16","1.0.2"}]), + ?assertMatch({ok, ExpectedResult}, + run_data(fix_rebar_brokenness("data2.txt"))). + +data3_test() -> + ExpectedResult = versionify([{"app68","0.0.1"}, + {"app58","0.0.1"}, + {"app48","0.0.7"}, + {"app38","0.0.1"}, + {"app28","0.0.1"}, + {"app18","0.0.1"}, + {"app4","0.0.1"}, + {"app1","0.0.1"}, + {"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg5","0.0.2"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}, + {"dep_pkg16","1.0.2"}]), + ?assertMatch({ok,ExpectedResult}, run_data(fix_rebar_brokenness("data3.txt"))). + +data4_test() -> + ExpectedResult = versionify([{"dep_pkg20","0.0.2"}, + {"app78","0.0.1"}, + {"app68","0.0.1"}, + {"app58","0.0.1"}, + {"app48","0.0.7"}, + {"app38","0.0.1"}, + {"app28","0.0.1"}, + {"app18","0.0.1"}, + {"app4","0.0.1"}, + {"app1","0.0.1"}, + {"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg5","0.0.2"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}, + {"dep_pkg16","1.0.2"}]), + ?assertMatch({ok, ExpectedResult}, + run_data(fix_rebar_brokenness("data4.txt"))). + +data5_test() -> + ExpectedResult = versionify([{"dep_pkg14","0.0.2"}, + {"dep_pkg22","0.0.2"}, + {"dep_pkg20","0.0.2"}, + {"app78","0.0.1"}, + {"app68","0.0.1"}, + {"app58","0.0.1"}, + {"app48","0.0.7"}, + {"app38","0.0.1"}, + {"app28","0.0.1"}, + {"app18","0.0.1"}, + {"app4","0.0.1"}, + {"app1","0.0.1"}, + {"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg5","0.0.2"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}, + {"dep_pkg16","1.0.2"}]), + ?assertMatch({ok, ExpectedResult}, + run_data(fix_rebar_brokenness("data5.txt"))). + +data6_test() -> + ExpectedResult = versionify([{"app108","0.0.1"}, + {"app98","0.0.1"}, + {"app88","0.0.1"}, + {"dep_pkg14","0.0.2"}, + {"dep_pkg22","0.0.2"}, + {"dep_pkg20","0.0.2"}, + {"app78","0.0.1"}, + {"app68","0.0.1"}, + {"app58","0.0.1"}, + {"app48","0.0.7"}, + {"app38","0.0.1"}, + {"app28","0.0.1"}, + {"app18","0.0.1"}, + {"app4","0.0.1"}, + {"app1","0.0.1"}, + {"app6","0.0.1"}, + {"dep_pkg13","0.0.2"}, + {"app13","0.0.1"}, + {"dep_pkg5","0.0.2"}, + {"dep_pkg1","0.0.2"}, + {"dep_pkg2","0.0.5"}, + {"dep_pkg7","0.1.2"}, + {"app9","0.0.1"}, + {"dep_pkg16","1.0.2"}]), + ?assertMatch({ok, ExpectedResult}, + run_data(fix_rebar_brokenness("data6.txt"))). + +log_07be9e47_test() -> + Data = run_log(fix_rebar_brokenness("log-07be9e47-6f42-4a5d-b8b5-1d2eae1ad83b.txt")), + ExpectedResult = versionify([{"0","0"}, + {"1","0"}, + {"3","0"}, + {"4","0"}, + {"5","0"}, + {"6","0"}, + {"7","0"}, + {"8","0"}, + {"9","0"}, + {"10","0"}, + {"11","0"}, + {"12","0"}, + {"13","0"}, + {"14","0"}, + {"15","0"}, + {"16","0"}, + {"18","0"}, + {"19","0"}, + {"21","0"}, + {"22","0"}, + {"23","0"}, + {"24","0"}, + {"25","0"}]), + ?assertMatch({ok, ExpectedResult}, + Data). + +log_183998c1_test() -> + ?assertMatch({error, {unreachable_package,<<"9">>}}, + run_log(fix_rebar_brokenness("log-183998c1-2ada-4214-b308-e480345c42f2.txt"))). + + +log_311a15e7_test() -> + {ok, Data} = run_log(fix_rebar_brokenness("log-311a15e7-3378-4c5b-beb7-86a1b9cf0ea9.txt")), + ExpectedResult = lists:sort(versionify([{"45", "22"}, + {"40","1"}, + {"3","5"}, + {"9","0"}, + {"8","0"}, + {"7","0"}, + {"6","2"}, + {"1","5"}, + {"0","2"}, + {"61","1"}, + {"60","0"}, + {"35","4"}, + {"39","0"}, + {"38","2"}, + {"37","2"}, + {"36","3"}, + {"32","24"}, + {"30","0"}, + {"19","1"}, + {"18","0"}, + {"17","2"}, + {"16","0"}, + {"15","0"}, + {"14","1"}, + {"13","0"}, + {"12","1"}, + {"11","0"}, + {"10","1"}, + {"59","0"}, + {"58","1"}, + {"57","0"}, + {"56","0"}, + {"55","4"}, + {"29","2"}, + {"27","2"}, + {"26","0"}, + {"25","5"}, + {"24","3"}, + {"23","1"}, + {"22","3"}, + {"21","2"}, + {"20","0"}])), + ?assertMatch(ExpectedResult, lists:sort(Data)). + +log_382cfe5b_test() -> + {ok, Data} = + run_log(fix_rebar_brokenness("log-382cfe5b-0ac2-48b8-83d1-717cb4620990.txt")), + ExpectedResult = lists:sort(versionify([{"18","0"}, + {"17","0"}, + {"15","1"}, + {"14","0"}, + {"10","0"}, + {"7","0"}, + {"6","0"}, + {"5","0"}, + {"4","0"}, + {"3","0"}, + {"2","1"}, + {"1","0"}, + {"0","0"}])), + ?assertMatch(ExpectedResult, lists:sort(Data)). + +log_d3564ef6_test() -> + {ok, Data} = run_log(fix_rebar_brokenness("log-d3564ef6-6437-41e7-90b6-dbdb849551a6_mod.txt")), + ExpectedResult = lists:sort(versionify([{"57","5"}, + {"56","3"}, + {"55","4"}, + {"54","0"}, + {"53","1"}, + {"82","0"}, + {"81","0"}, + {"80","1"}, + {"29","0"}, + {"28","5"}, + {"27","3"}, + {"26","1"}, + {"25","3"}, + {"24","2"}, + {"23","0"}, + {"22","1"}, + {"21","0"}, + {"20","2"}, + {"75","32"}, + {"79","2"}, + {"78","4"}, + {"74","7"}, + {"73","11"}, + {"72","0"}, + {"70","1"}, + {"47","4"}, + {"45","1"}, + {"44","1"}, + {"43","7"}, + {"42","1"}, + {"41","2"}, + {"40","2"}, + {"19","0"}, + {"18","0"}, + {"17","1"}, + {"16","0"}, + {"15","1"}, + {"14","0"}, + {"13","1"}, + {"12","0"}, + {"11","0"}, + {"10","0"}, + {"9","2"}, + {"4","5"}, + {"3","2"}, + {"0","3"}, + {"69","0"}, + {"68","1"}, + {"67","7"}, + {"39","3"}, + {"35","24"}, + {"33","0"}, + {"32","2"}, + {"30","2"}])), + ?assertMatch(ExpectedResult, lists:sort(Data)). + +log_ea2d264b_test() -> + {ok, Data} = run_log(fix_rebar_brokenness("log-ea2d264b-003e-4611-94ed-14efc7732083.txt")), + ExpectedResult = lists:sort(versionify([{"18","1"}, + {"17","0"}, + {"16","0"}, + {"15","0"}, + {"14","0"}, + {"13","1"}, + {"10","1"}, + {"9","1"}, + {"8","2"}, + {"6","0"}, + {"5","0"}, + {"4","0"}, + {"3","0"}, + {"2","0"}, + {"1","0"}, + {"0","1"}])), + ?assertMatch(ExpectedResult, lists:sort(Data)). + +%%============================================================================ +%% Internal Functions +%%============================================================================ +versionify(X) when erlang:is_list(X) -> + lists:map(fun versionify/1, X); +versionify({K, V}) -> + {erlang:list_to_binary(K), rcl_depsolver:parse_version(V)}. + +fix_rebar_brokenness(Filename) -> + Alt1 = filename:join(["./test", "data", Filename]), + Alt2 = filename:join(["../test", "data", Filename]), + case filelib:is_regular(Alt1) of + true -> + Alt1; + false -> + case filelib:is_regular(Alt2) of + true -> + Alt2; + false -> + io:format("~p~n", [Alt2]), + erlang:throw(unable_to_find_data_files) + end + end. + +run_data_file(Device) -> + Constraints = get_constraints(io:get_line(Device, "")), + rcl_depsolver:solve(process_packages(read_packages(Device)), Constraints). + +goble_lines(_Device, eof, Acc) -> + lists:reverse(Acc); +goble_lines(_Device, {error, Err}, _Acc) -> + erlang:throw(Err); +goble_lines(Device, ValidVal, Acc) -> + goble_lines(Device, io:get_line(Device, ""), [ValidVal | Acc]). + +goble_lines(Device) -> + goble_lines(Device, io:get_line(Device, ""), []). + +run_log_file(Device) -> + State0 = rcl_depsolver:new_graph(), + {Goals, State2} = + lists:foldl(fun(Line, Data) -> + process_add_goal(Line, + process_add_constraint(Line, + process_add_package(Line, Data))) + end, {[], State0}, goble_lines(Device)), + rcl_depsolver:solve(State2, Goals). + +read_packages(Device) -> + process_line(Device, io:get_line(Device, ""), []). + +process_line(Device, eof, Acc) -> + file:close(Device), + Acc; +process_line(Device, [], Acc) -> + process_line(Device, io:get_line(Device, ""), + Acc); +process_line(Device, "\n", Acc) -> + process_line(Device, io:get_line(Device, ""), + Acc); +process_line(Device, [$\s | Rest], [{Pkg, Vsn, Deps} | Acc]) -> + [DepPackage, Type, DepVsn] = string:tokens(Rest, " \n"), + Dep = + case Type of + "=" -> + {DepPackage, DepVsn}; + ">=" -> + {DepPackage, DepVsn, gte} + end, + process_line(Device, io:get_line(Device, ""), + [{Pkg, Vsn, [Dep | Deps]} | Acc]); +process_line(Device, Pkg, Acc) -> + [Package, Vsn] = string:tokens(Pkg, " \n"), + process_line(Device, io:get_line(Device, ""), + [{Package, Vsn, []} | Acc]). + +process_packages(Pkgs) -> + lists:foldl(fun({Pkg, Vsn, Constraints}, Dom0) -> + rcl_depsolver:add_package_version(Dom0, Pkg, Vsn, Constraints) + end, rcl_depsolver:new_graph(), Pkgs). + +get_constraints(ConLine) -> + AppVsns = string:tokens(ConLine, " \n"), + lists:map(fun(AppCon) -> + parse_app(AppCon, []) + end, AppVsns). +parse_app([$= | Rest], Acc) -> + {lists:reverse(Acc), Rest}; +parse_app([$>, $= | Rest], Acc) -> + {lists:reverse(Acc), Rest, gte}; +parse_app([Else | Rest], Acc) -> + parse_app(Rest, [Else | Acc]); +parse_app([], Acc) -> + lists:reverse(Acc). + +process_add_package(Line, {Goals, State0}) -> + case re:run(Line, ?ADD_PKG, [{capture, all, list}]) of + {match, [_All, _InstNumber, PkgName, _PkgCount, VersionCount]} -> + {Goals, + lists:foldl(fun(PkgVsn, State1) -> + rcl_depsolver:add_package_version(State1, + PkgName, + erlang:integer_to_list(PkgVsn), + []) + end, State0, lists:seq(0, + erlang:list_to_integer(VersionCount)))}; + _ -> + {Goals, State0} + end. + +process_add_constraint(Line, {Goals, State0}) -> + case re:run(Line, ?ADD_VC, [{capture, all, list}]) of + {match, [_All, _InstNumber, Pkg, Vsn, Dep, _Ignore, DepVsn]} -> + {Goals, + rcl_depsolver:add_package_version(State0, Pkg, Vsn, [{Dep, DepVsn}])}; + _ -> + {Goals, State0} + end. + +process_add_goal(Line, {Goals, State0}) -> + case re:run(Line, ?ADD_GOAL, [{capture, all, list}]) of + {match,[_All, _InstNumber, NewGoal]} -> + {[NewGoal | Goals], State0}; + _ -> + {Goals, State0} + end. diff --git a/test/rlx_depsolver_tests.erl b/test/rlx_depsolver_tests.erl new file mode 100644 index 0000000..eae31a4 --- /dev/null +++ b/test/rlx_depsolver_tests.erl @@ -0,0 +1,495 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%% ex: ts=4 sx=4 et +%% +%%------------------------------------------------------------------- +%% Copyright 2012 Opscode, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% @author Eric Merritt +%%------------------------------------------------------------------- +-module(rcl_depsolver_tests). + +-include_lib("eunit/include/eunit.hrl"). + +%%============================================================================ +%% Tests +%%============================================================================ + +first_test() -> + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2+build.33"}, + {app3, "0.2", '>='}]}, + {"0.2", []}, + {"0.3", []}]}, + {app2, [{"0.1", []}, + {"0.2+build.33",[{app3, "0.3"}]}, + {"0.3", []}]}, + {app3, [{"0.1", []}, + {"0.2", []}, + {"0.3", []}]}]), + + + case rcl_depsolver:solve(Dom0, [{app1, "0.1"}]) of + {ok,[{app3,{{0,3},{[],[]}}}, + {app2,{{0,2},{[],[<<"build">>,33]}}}, + {app1,{{0,1},{[],[]}}}]} -> + ok; + E -> + erlang:throw({invalid_result, E}) + end. + +second_test() -> + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.1", '>='}, + {app4, "0.2"}, + {app3, "0.2", '>='}]}, + {"0.2", []}, + {"0.3", []}]}, + {app2, [{"0.1", [{app3, "0.2", gte}]}, + {"0.2", [{app3, "0.2", gte}]}, + {"0.3", [{app3, "0.2", '>='}]}]}, + {app3, [{"0.1", [{app4, "0.2", '>='}]}, + {"0.2", [{app4, "0.2"}]}, + {"0.3", []}]}, + {app4, [{"0.1", []}, + {"0.2", [{app2, "0.2", gte}, + {app3, "0.3"}]}, + {"0.3", []}]}]), + + X = rcl_depsolver:solve(Dom0, [{app1, "0.1"}, + {app2, "0.3"}]), + + ?assertMatch({ok, [{app3,{{0,3},{[],[]}}}, + {app2,{{0,3},{[],[]}}}, + {app4,{{0,2},{[],[]}}}, + {app1,{{0,1},{[],[]}}}]}, + X). + +third_test() -> + + Pkg1Deps = [{app2, "0.1.0", '>='}, + {app3, "0.1.1", "0.1.5", between}], + + Pkg2Deps = [{app4, "5.0.0", gte}], + Pkg3Deps = [{app5, "2.0.0", '>='}], + Pkg4Deps = [app5], + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + {"0.2", Pkg1Deps}, + {"3.0", Pkg1Deps}]}, + {app2, [{"0.0.1", Pkg2Deps}, + {"0.1", Pkg2Deps}, + {"1.0", Pkg2Deps}, + {"3.0", Pkg2Deps}]}, + {app3, [{"0.1.0", Pkg3Deps}, + {"0.1.3", Pkg3Deps}, + {"2.0.0", Pkg3Deps}, + {"3.0.0", Pkg3Deps}, + {"4.0.0", Pkg3Deps}]}, + {app4, [{"0.1.0", Pkg4Deps}, + {"0.3.0", Pkg4Deps}, + {"5.0.0", Pkg4Deps}, + {"6.0.0", Pkg4Deps}]}, + {app5, [{"0.1.0", []}, + {"0.3.0", []}, + {"2.0.0", []}, + {"6.0.0", []}]}]), + + ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{6,0,0},{[],[]}}}, + {app2,{{3,0},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), + + + ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{6,0,0},{[],[]}}}, + {app2,{{3,0},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [app1])). + +fail_test() -> + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + [{app1, [{"0.1", [{app2, "0.2"}, + {app3, "0.2", gte}]}, + {"0.2", []}, + {"0.3", []}]}, + {app2, [{"0.1", []}, + {"0.2",[{app3, "0.1"}]}, + {"0.3", []}]}, + {app3, [{"0.1", []}, + {"0.2", []}, + {"0.3", []}]}]), + + Ret = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), + %% We do this to make sure all errors can be formated. + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, + [{[{[{app1,{{0,1},{[],[]}}}], + [{app1,{{0,1},{[],[]}}},[[{app2,{{0,2},{[],[]}}}]]]}], + [{{app2,{{0,2},{[],[]}}},[{app3,{{0,1},{[],[]}}}]}, + {{app1,{{0,1},{[],[]}}},[{app3,{{0,2},{[],[]}},gte}]}]}]}, + Ret). + +conflicting_passing_test() -> + Pkg1Deps = [{app2, "0.1.0", '>='}, + {app5, "2.0.0"}, + {app4, "0.3.0", "5.0.0", between}, + {app3, "0.1.1", "0.1.5", between}], + + Pkg2Deps = [{app4, "3.0.0", gte}], + Pkg3Deps = [{app5, "2.0.0", '>='}], + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + {"0.1.0", Pkg1Deps}, + {"0.2", Pkg1Deps}, + {"3.0", Pkg1Deps}]}, + {app2, [{"0.0.1", Pkg2Deps}, + {"0.1", Pkg2Deps}, + {"1.0", Pkg2Deps}, + {"3.0", Pkg2Deps}]}, + {app3, [{"0.1.0", Pkg3Deps}, + {"0.1.3", Pkg3Deps}, + {"2.0.0", Pkg3Deps}, + {"3.0.0", Pkg3Deps}, + {"4.0.0", Pkg3Deps}]}, + {app4, [{"0.1.0", [{app5, "0.1.0"}]}, + {"0.3.0", [{app5, "0.3.0"}]}, + {"5.0.0", [{app5, "2.0.0"}]}, + {"6.0.0", [{app5, "6.0.0"}]}]}, + {app5, [{"0.1.0", []}, + {"0.3.0", []}, + {"2.0.0", []}, + {"6.0.0", []}]}]), + + ?assertMatch({ok, [{app5,{{2,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{5,0,0},{[],[]}}}, + {app2,{{3,0},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), + + ?assertMatch({ok, [{app5,{{2,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{5,0,0},{[],[]}}}, + {app2,{{3,0},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [app1, app2, app5])). + + + +circular_dependencies_test() -> + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", [app2]}]}, + {app2, [{"0.0.1", [app1]}]}]), + + ?assertMatch({ok, [{app1,{{0,1,0},{[],[]}}},{app2,{{0,0,1},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [{app1, "0.1.0"}])). + +conflicting_failing_test() -> + Pkg1Deps = [app2, + {app5, "2.0.0", '='}, + {app4, "0.3.0", "5.0.0", between}], + + Pkg2Deps = [{app4, "5.0.0", gte}], + Pkg3Deps = [{app5, "6.0.0"}], + + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"3.0", Pkg1Deps}]}, + {app2, [{"0.0.1", Pkg2Deps}]}, + {app3, [{"0.1.0", Pkg3Deps}]}, + {app4, [{"5.0.0", [{app5, "2.0.0"}]}]}, + {app5, [{"2.0.0", []}, + {"6.0.0", []}]}]), + Ret = rcl_depsolver:solve(Dom0, [app1, app3]), + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, + [{[{[app1], + [{app1,{{3,0},{[],[]}}}, + [[{app4,{{5,0,0},{[],[]}}}], + [{app2,{{0,0,1},{[],[]}}},[[{app4,{{5,0,0},{[],[]}}}]]]]]}, + {[app3], + [{app3,{{0,1,0},{[],[]}}},[[{app5,{{6,0,0},{[],[]}}}]]]}], + [{{app4,{{5,0,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}}}]}, + {{app1,{{3,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}},'='}]}]}]}, + Ret). + + +pessimistic_major_minor_patch_test() -> + + Pkg1Deps = [{app2, "2.1.1", '~>'}, + {app3, "0.1.1", "0.1.5", between}], + + Pkg2Deps = [{app4, "5.0.0", gte}], + Pkg3Deps = [{app5, "2.0.0", '>='}], + Pkg4Deps = [app5], + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + {"0.2", Pkg1Deps}, + {"3.0", Pkg1Deps}]}, + {app2, [{"0.0.1", Pkg2Deps}, + {"0.1", Pkg2Deps}, + {"1.0", Pkg2Deps}, + {"2.1.5", Pkg2Deps}, + {"2.2", Pkg2Deps}, + {"3.0", Pkg2Deps}]}, + {app3, [{"0.1.0", Pkg3Deps}, + {"0.1.3", Pkg3Deps}, + {"2.0.0", Pkg3Deps}, + {"3.0.0", Pkg3Deps}, + {"4.0.0", Pkg3Deps}]}, + {app4, [{"0.1.0", Pkg4Deps}, + {"0.3.0", Pkg4Deps}, + {"5.0.0", Pkg4Deps}, + {"6.0.0", Pkg4Deps}]}, + {app5, [{"0.1.0", []}, + {"0.3.0", []}, + {"2.0.0", []}, + {"6.0.0", []}]}]), + ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{6,0,0},{[],[]}}}, + {app2,{{2,1,5},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). + +pessimistic_major_minor_test() -> + + Pkg1Deps = [{app2, "2.1", '~>'}, + {app3, "0.1.1", "0.1.5", between}], + + Pkg2Deps = [{app4, "5.0.0", gte}], + Pkg3Deps = [{app5, "2.0.0", '>='}], + Pkg4Deps = [app5], + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + {"0.2", Pkg1Deps}, + {"3.0", Pkg1Deps}]}, + {app2, [{"0.0.1", Pkg2Deps}, + {"0.1", Pkg2Deps}, + {"1.0", Pkg2Deps}, + {"2.1.5", Pkg2Deps}, + {"2.2", Pkg2Deps}, + {"3.0", Pkg2Deps}]}, + {app3, [{"0.1.0", Pkg3Deps}, + {"0.1.3", Pkg3Deps}, + {"2.0.0", Pkg3Deps}, + {"3.0.0", Pkg3Deps}, + {"4.0.0", Pkg3Deps}]}, + {app4, [{"0.1.0", Pkg4Deps}, + {"0.3.0", Pkg4Deps}, + {"5.0.0", Pkg4Deps}, + {"6.0.0", Pkg4Deps}]}, + {app5, [{"0.1.0", []}, + {"0.3.0", []}, + {"2.0.0", []}, + {"6.0.0", []}]}]), + ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, + {app3,{{0,1,3},{[],[]}}}, + {app4,{{6,0,0},{[],[]}}}, + {app2,{{2,2},{[],[]}}}, + {app1,{{3,0},{[],[]}}}]}, + rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). + +filter_versions_test() -> + + Cons = [{app2, "2.1", '~>'}, + {app3, "0.1.1", "0.1.5", between}, + {app4, "5.0.0", gte}, + {app5, "2.0.0", '>='}, + app5], + + Packages = [{app1, "0.1.0"}, + {app1, "0.2"}, + {app1, "0.2"}, + {app1, "3.0"}, + {app2, "0.0.1"}, + {app2, "0.1"}, + {app2, "1.0"}, + {app2, "2.1.5"}, + {app2, "2.2"}, + {app2, "3.0"}, + {app3, "0.1.0"}, + {app3, "0.1.3"}, + {app3, "2.0.0"}, + {app3, "3.0.0"}, + {app3, "4.0.0"}, + {app4, "0.1.0"}, + {app4, "0.3.0"}, + {app4, "5.0.0"}, + {app4, "6.0.0"}, + {app5, "0.1.0"}, + {app5, "0.3.0"}, + {app5, "2.0.0"}, + {app5, "6.0.0"}], + + ?assertMatch({ok, [{app1,"0.1.0"}, + {app1,"0.2"}, + {app1,"0.2"}, + {app1,"3.0"}, + {app2,"2.1.5"}, + {app2,"2.2"}, + {app3,"0.1.3"}, + {app4,"5.0.0"}, + {app4,"6.0.0"}, + {app5,"2.0.0"}, + {app5,"6.0.0"}]}, + rcl_depsolver:filter_packages(Packages, Cons)), + + Ret = rcl_depsolver:filter_packages(Packages, + [{"foo", "1.0.0", '~~~~'} | Cons]), + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, {invalid_constraints, [{<<"foo">>,{{1,0,0},{[],[]}},'~~~~'}]}}, Ret). + + +-spec missing_test() -> ok. +missing_test() -> + + Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, + {app3, "0.2", '>='}, + {app4, "0.2", '='}]}, + {"0.2", [{app4, "0.2"}]}, + {"0.3", [{app4, "0.2", '='}]}]}, + {app2, [{"0.1", []}, + {"0.2",[{app3, "0.3"}]}, + {"0.3", []}]}, + {app3, [{"0.1", []}, + {"0.2", []}, + {"0.3", []}]}]), + Ret1 = rcl_depsolver:solve(Dom0, [{app4, "0.1"}, {app3, "0.1"}]), + _ = rcl_depsolver:format_error(Ret1), + ?assertMatch({error,{unreachable_package,app4}}, Ret1), + + Ret2 = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), + _ = rcl_depsolver:format_error(Ret2), + ?assertMatch({error,{unreachable_package,app4}}, + Ret2). + + +binary_test() -> + + World = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, + {<<"bar">>, [{<<"2.0.0">>, [{<<"foo">>, <<"3.0.0">>, gt}]}]}], + Ret = rcl_depsolver:solve(rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + World), + [<<"foo">>]), + + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, + [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], + [{{<<"foo">>,{{1,2,3},{[],[]}}}, + [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). + +%% +%% We don't have bar cookbook +%% +%% Ruby gives +%% "message":"Unable to satisfy constraints on cookbook bar, which does not +%% exist, due to run list item (foo >= 0.0.0). Run list items that may result +%% in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", +%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", +%% "non_existent_cookbooks":["bar"]," +%% "most_constrained_cookbooks":[]}" +%% +doesnt_exist_test() -> + Constraints = [{<<"foo">>,[{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}], + World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), + Ret = rcl_depsolver:solve(World, [<<"foo">>]), + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error,{unreachable_package,<<"bar">>}}, Ret). + +%% +%% We have v 2.0.0 of bar but want > 2.0.0 +%% +%% Ruby gives +%% "message":"Unable to satisfy constraints on cookbook bar due to run list item +%% (foo >= 0.0.0). Run list items that may result in a constraint on bar: [(foo +%% = 1.2.3) -> (bar > 2.0.0)]", +%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", +%% "non_existent_cookbooks":[], +%% "most_constrained_cookbooks":["bar 2.0.0 -> []"] +%% +not_new_enough_test() -> + + Constraints = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, + {<<"bar">>, [{<<"2.0.0">>, []}]}], + World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), + Ret = rcl_depsolver:solve(World, [<<"foo">>]), + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, + [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], + [{{<<"foo">>,{{1,2,3},{[],[]}}}, + [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). + +%% +%% circular deps are bad +%% +%% Ruby gives +%% "message":"Unable to satisfy constraints on cookbook bar due to run list item (foo >= 0.0.0). +%% Run list items that may result in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", +%% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", +%% "non_existent_cookbooks":[], +%% "most_constrained_cookbooks:["bar = 2.0.0 -> [(foo > 3.0.0)]"] +%% +impossible_dependency_test() -> + World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + [{<<"foo">>, [{<<"1.2.3">>,[{ <<"bar">>, <<"2.0.0">>, gt}]}]}, + {<<"bar">>, [{<<"2.0.0">>, [{ <<"foo">>, <<"3.0.0">>, gt}]}]}]), + Ret = rcl_depsolver:solve(World, [<<"foo">>]), + _ = rcl_depsolver:format_error(Ret), + ?assertMatch({error, + [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], + [{{<<"foo">>,{{1,2,3},{[],[]}}}, + [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). + +%% +%% Formatting tests +%% +format_test_() -> + [{"format constraint", + [equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(<<"foo">>)), + equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(foo)), + equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}})), + equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '='})), + equal_bin_string(<<"(foo > 1.2.0)">>, + rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '>'})), + equal_bin_string(<<"(foo > 1.2.0)">>, + rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, gt})), + equal_bin_string(<<"(foo between 1.2.0 and 1.3.0)">>, + rcl_depsolver:format_constraint({<<"foo">>,{{1,2,0}, {[], []}}, + {{1,3,0}, {[], []}}, between})), + equal_bin_string(<<"(foo > 1.2.0-alpha.1+build.36)">>, + rcl_depsolver:format_constraint({<<"foo">>, + {{1,2,0}, {["alpha", 1], ["build", 36]}}, gt})) + ] + }, + {"format roots", + [equal_bin_string(<<"(bar = 1.2.0)">>, + rcl_depsolver:format_roots([ [{<<"bar">>, {{1,2,0},{[],[]}}}] ])), + equal_bin_string(<<"(bar = 1.2.0), foo">>, + rcl_depsolver:format_roots([[<<"foo">>, + {<<"bar">>, {{1,2,0},{[],[]}}}]])), + equal_bin_string(<<"(bar = 1.2.0), foo">>, + rcl_depsolver:format_roots([[<<"foo">>], [{<<"bar">>, {{1,2,0},{[],[]}}}]])) + ] + } + ]. + +%% +%% Internal functions +%% +equal_bin_string(Expected, Got) -> + ?_assertEqual(Expected, erlang:iolist_to_binary(Got)). diff --git a/test/rlx_discover_SUITE.erl b/test/rlx_discover_SUITE.erl new file mode 100644 index 0000000..e3a2861 --- /dev/null +++ b/test/rlx_discover_SUITE.erl @@ -0,0 +1,193 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Eric Merrit +%%% @copyright (C) 2012, Eric Merrit +-module(rclt_discover_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + all/0, + normal_case/1, + no_beam_case/1, + bad_ebin_case/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_, Config) -> + DataDir = proplists:get_value(data_dir, Config), + LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), + LibDir2 = filename:join([DataDir, create_random_name("lib_dir2_")]), + ok = rcl_util:mkdir_p(LibDir1), + ok = rcl_util:mkdir_p(LibDir2), + State = rcl_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), + [{lib1, LibDir1}, + {lib2, LibDir2}, + {state, State} | Config]. + + +all() -> + [normal_case, no_beam_case, bad_ebin_case]. + +normal_case(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + State0 = rcl_state:put(proplists:get_value(state, Config), + disable_default_libs, true), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + {ok, State2} = rcl_provider:do(DiscoverProvider, State1), + lists:foreach(fun(App) -> + ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + end, Apps1), + + lists:foreach(fun(App) -> + ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + end, Apps2), + Length = erlang:length(Apps2) + + erlang:length(Apps2), + ?assertMatch(Length, erlang:length(rcl_state:available_apps(State2))). + +no_beam_case(Config) -> + %% We silently ignore apps with no beams + LibDir1 = proplists:get_value(lib1, Config), + _Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + _Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + BadName = create_random_name("error_bad"), + BadVsn = create_random_vsn(), + AppDir = filename:join([LibDir2, BadName]), + write_app_file(AppDir, BadName, BadVsn), + State0 = proplists:get_value(state, Config), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + EbinDir = filename:join([LibDir2, BadName, <<"ebin">>]), + ?assertMatch({error, {_, [{no_beam_files, EbinDir}]}}, + rcl_provider:do(DiscoverProvider, State1)). + +bad_ebin_case(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + _Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + _Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + BadName = create_random_name("error_bad"), + BadVsn = create_random_vsn(), + AppDir = filename:join([LibDir2, BadName]), + Filename = filename:join([AppDir, <<"ebin">>, BadName ++ ".app"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_bad_app_metadata(BadName, BadVsn)), + write_beam_file(AppDir, BadName), + State0 = proplists:get_value(state, Config), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + ?assertMatch({error, {_, [{invalid_app_file, Filename}]}}, + rcl_provider:do(DiscoverProvider, State1)). + + +%%%=================================================================== +%%% Helper functions +%%%=================================================================== +create_app(Dir, Name, Vsn) -> + AppDir = filename:join([Dir, Name]), + write_app_file(AppDir, Name, Vsn), + write_beam_file(AppDir, Name), + {ok, App} = rcl_app_info:new(erlang:list_to_atom(Name), Vsn, + erlang:iolist_to_binary(AppDir), + [kernel, stdlib], []), + App. + +write_beam_file(Dir, Name) -> + Beam = filename:join([Dir, "ebin", "not_a_real_beam" ++ Name ++ ".beam"]), + ok = filelib:ensure_dir(Beam), + ok = ec_file:write_term(Beam, testing_purposes_only). + +write_app_file(Dir, Name, Version) -> + Filename = filename:join([Dir, "ebin", Name ++ ".app"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_app_metadata(Name, Version)). + +get_app_metadata(Name, Vsn) -> + {application, erlang:list_to_atom(Name), + [{description, ""}, + {vsn, Vsn}, + {modules, []}, + {applications, [kernel, stdlib]}]}. + +get_bad_app_metadata(Name, Vsn) -> + ["{application, ", Name, ", + [{description, \"\"}, + {vsn, \"", Vsn, "\"}, + {modules, [], + {applications, [kernel, stdlib]}]}."]. + + +create_random_name(Name) -> + random:seed(erlang:now()), + Name ++ erlang:integer_to_list(random:uniform(1000000)). + +create_random_vsn() -> + random:seed(erlang:now()), + lists:flatten([erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100))]). diff --git a/test/rlx_goal_tests.erl b/test/rlx_goal_tests.erl new file mode 100644 index 0000000..20fb5e5 --- /dev/null +++ b/test/rlx_goal_tests.erl @@ -0,0 +1,63 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% @doc test for target spec parsing +-module(rclt_goal). + +-include_lib("eunit/include/eunit.hrl"). + +parse_test() -> + ?assertMatch({ok, getopt}, + rcl_goal:parse("getopt")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, + rcl_goal:parse("getopt=0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, + rcl_goal:parse("getopt:0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, + rcl_goal:parse("getopt-0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, + rcl_goal:parse("getopt >= 0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, + rcl_goal:parse("getopt:gte:0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, + rcl_goal:parse("getopt>0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, + rcl_goal:parse("getopt:gt:0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, + rcl_goal:parse("getopt<= 0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, + rcl_goal:parse("getopt:lte:0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lt}}, + rcl_goal:parse("getopt<0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, + rcl_goal:parse("getopt ~>0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, + rcl_goal:parse("getopt: pes:0.5.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, + rcl_goal:parse("getopt:btwn:0.5.1,0.6.1")), + ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, + rcl_goal:parse("getopt:between :0.5.1,0.6.1")). + +fail_test() -> + ?assertMatch({fail,_}, + rcl_goal:parse("got:")), + ?assertMatch({fail,_}, + rcl_goal:parse("between:btwn:0.5")), + ?assertMatch({fail,_}, + rcl_goal:parse("between:btwn:0.5,")). diff --git a/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl new file mode 100644 index 0000000..90d99c9 --- /dev/null +++ b/test/rlx_release_SUITE.erl @@ -0,0 +1,837 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%------------------------------------------------------------------- +%%% @author Eric Merrit +%%% @copyright (C) 2012, Eric Merrit +-module(rclt_release_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + all/0, + make_release/1, + make_scriptless_release/1, + make_overridden_release/1, + make_skip_app_release/1, + make_rerun_overridden_release/1, + make_implicit_config_release/1, + overlay_release/1, + make_goalless_release/1, + make_depfree_release/1, + make_invalid_config_release/1, + make_relup_release/1, + make_relup_release2/1, + make_one_app_top_level_release/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("kernel/include/file.hrl"). +-include_lib("kernel/include/file.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_, Config) -> + DataDir = proplists:get_value(data_dir, Config), + LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), + ok = rcl_util:mkdir_p(LibDir1), + State = rcl_state:new([{lib_dirs, [LibDir1]}], release), + [{lib1, LibDir1}, + {state, State} | Config]. + +all() -> + [make_release, make_scriptless_release, make_overridden_release, + make_skip_app_release, + make_implicit_config_release, make_rerun_overridden_release, + overlay_release, make_goalless_release, make_depfree_release, + make_invalid_config_release, make_relup_release, make_relup_release2, + make_one_app_top_level_release]. + +make_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + +make_invalid_config_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + ok = ec_file:write(ConfigFile, + "{release, {foo, \"0.0.1\"}, + [goal_app_1, + goal_app_2,]}"), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {error, {rcl_prv_config, + {consult, _, _}}} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile). + +make_scriptless_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{generate_start_script, false}, + {release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile), + + ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo"]))), + ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo-0.0.1"]))), + + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + + +make_overridden_release(Config) -> + DataDir = proplists:get_value(data_dir, Config), + OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + OverrideApp = create_random_name("override_app"), + OverrideVsn = create_random_vsn(), + OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" ++ OverrideVsn), + OverrideAppName = erlang:list_to_atom(OverrideApp), + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + erlang:list_to_atom(OverrideApp), + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, Cwd} = file:get_cwd(), + {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + OutputDir, [{OverrideAppName, OverrideAppDir}], + ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({OverrideAppName, OverrideVsn}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), + {ok, Real} = file:read_link(filename:join([OutputDir, "lib", + OverrideApp ++ "-" ++ OverrideVsn])), + ?assertMatch(OverrideAppDir, Real). + +make_skip_app_release(Config) -> + DataDir = proplists:get_value(data_dir, Config), + SkipAppDir1 = filename:join([DataDir, create_random_name("skip_app_dir_")]), + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + SkipAppApp = create_random_name("skip_app_app"), + SkipAppVsn = create_random_vsn(), + SkipAppAppName = erlang:list_to_atom(SkipAppApp), + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + create_empty_app(SkipAppDir1, SkipAppApp, SkipAppVsn, [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}, + {skip_apps, [erlang:list_to_atom(SkipAppApp)]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, Cwd} = file:get_cwd(), + {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + OutputDir, [], + ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assertNot(lists:member({SkipAppAppName, SkipAppVsn}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + +make_implicit_config_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + FooRoot = filename:join([LibDir1, "foodir1", "foodir2"]), + filelib:ensure_dir(filename:join([FooRoot, "tmp"])), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + ok = file:set_cwd(FooRoot), + {ok, FooRoot} = file:get_cwd(), + {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, undefined), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + ?assert(ec_file:exists(OutputDir)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + +make_rerun_overridden_release(Config) -> + DataDir = proplists:get_value(data_dir, Config), + OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + OverrideApp = create_random_name("override_app"), + OverrideVsn = create_random_vsn(), + OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" + ++ OverrideVsn), + OverrideAppName = erlang:list_to_atom(OverrideApp), + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + erlang:list_to_atom(OverrideApp), + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, Cwd} = file:get_cwd(), + {ok, _} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + OutputDir, [{OverrideAppName, OverrideAppDir}], + ConfigFile), + + %% Now we run it again to see if it fails. + {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2, + OutputDir, [{OverrideAppName, OverrideAppDir}], + ConfigFile), + + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({OverrideAppName, OverrideVsn}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), + {ok, Real} = file:read_link(filename:join([OutputDir, "lib", + OverrideApp ++ "-" ++ OverrideVsn])), + ?assertMatch(OverrideAppDir, Real). + +overlay_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + OverlayVars = filename:join([LibDir1, "vars.config"]), + Template = filename:join([LibDir1, "test_template"]), + write_config(ConfigFile, + [{overlay_vars, OverlayVars}, + {overlay, [{mkdir, "{{target_dir}}/fooo"}, + {copy, OverlayVars, + "{{target_dir}}/{{foo_dir}}/vars.config"}, + {copy, OverlayVars, + "{{target_dir}}/{{yahoo}}/"}, + {template, Template, + "{{target_dir}}/test_template_resolved"}]}, + {release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + + VarsFile = filename:join([LibDir1, "vars.config"]), + write_config(VarsFile, [{yahoo, "yahoo"}, + {yahoo2, [{foo, "bar"}]}, + {yahoo3, [{bar, "{{yahoo}}/{{yahoo2.foo}}"}]}, + {foo_dir, "foodir"}]), + + TemplateFile = filename:join([LibDir1, "test_template"]), + ok = file:write_file(TemplateFile, test_template_contents()), + {ok, FileInfo} = file:read_file_info(TemplateFile), + ok = file:write_file_info(TemplateFile, FileInfo#file_info{mode=8#00777}), + + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + + {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile), + + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), + + ?assert(ec_file:exists(filename:join(OutputDir, "fooo"))), + ?assert(ec_file:exists(filename:join([OutputDir, "foodir", "vars.config"]))), + ?assert(ec_file:exists(filename:join([OutputDir, "yahoo", "vars.config"]))), + + TemplateData = case file:consult(filename:join([OutputDir, "test_template_resolved"])) of + {ok, Details} -> + Details; + Error -> + erlang:throw({failed_to_consult, Error}) + end, + {ok, ReadFileInfo} = file:read_file_info(filename:join([OutputDir, "test_template_resolved"])), + ?assertEqual(8#100777, ReadFileInfo#file_info.mode), + + ?assertEqual(erlang:system_info(version), + proplists:get_value(erts_vsn, TemplateData)), + ?assertEqual(erlang:system_info(version), + proplists:get_value(release_erts_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(release_version, TemplateData)), + ?assertEqual(foo, + proplists:get_value(release_name, TemplateData)), + ?assertEqual([kernel,stdlib,lib_dep_1,non_goal_2,non_goal_1, + goal_app_1,goal_app_2], + proplists:get_value(release_applications, TemplateData)), + ?assert(proplists:is_defined(std_version, TemplateData)), + ?assert(proplists:is_defined(kernel_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(non_goal_1_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(non_goal_2_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(goal_app_1_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(goal_app_2_version, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(lib_dep_1, TemplateData)), + ?assert(proplists:is_defined(lib_dep_1_dir, TemplateData)), + ?assertEqual([stdlib,kernel], + proplists:get_value(lib_dep_1_active, TemplateData)), + ?assertEqual([], + proplists:get_value(lib_dep_1_library, TemplateData)), + ?assertEqual("false", + proplists:get_value(lib_dep_1_link, TemplateData)), + ?assertEqual("(2:debug)", + proplists:get_value(log, TemplateData)), + ?assertEqual(OutputDir, + proplists:get_value(output_dir, TemplateData)), + ?assertEqual(OutputDir, + proplists:get_value(target_dir, TemplateData)), + ?assertEqual([], + proplists:get_value(overridden, TemplateData)), + ?assertEqual([""], + proplists:get_value(goals, TemplateData)), + ?assert(proplists:is_defined(lib_dirs, TemplateData)), + ?assert(proplists:is_defined(config_file, TemplateData)), + ?assertEqual([""], + proplists:get_value(goals, TemplateData)), + ?assertEqual([], + proplists:get_value(sys_config, TemplateData)), + ?assert(proplists:is_defined(root_dir, TemplateData)), + ?assertEqual(foo, + proplists:get_value(default_release_name, TemplateData)), + ?assertEqual("0.0.1", + proplists:get_value(default_release_version, TemplateData)), + ?assertEqual("foo-0.0.1", + proplists:get_value(default_release, TemplateData)), + ?assertEqual("yahoo", + proplists:get_value(yahoo, TemplateData)), + ?assertEqual("bar", + proplists:get_value(yahoo2_foo, TemplateData)), + ?assertEqual("foodir", + proplists:get_value(foo_dir, TemplateData)), + ?assertEqual("yahoo/bar", + proplists:get_value(yahoo3, TemplateData)). + +make_goalless_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + []}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + ?assertMatch({error,{rcl_prv_release,no_goals_specified}}, + relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile)). + +make_depfree_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [kernel,stdlib], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [kernel,stdlib], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [kernel,stdlib], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [kernel,stdlib], []), + create_app(LibDir1, "non_goal_2", "0.0.1", [kernel,stdlib], []), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + OutputDir, ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)). + +make_relup_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + write_appup_file(GA1, "0.0.2"), + write_appup_file(GA2, "0.0.2"), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [sasl, + {goal_app_1, "0.0.1"}, + {goal_app_2, "0.0.1"}]}, + {release, {foo, "0.0.2"}, + [sasl, + {goal_app_1, "0.0.2"}, + {goal_app_2, "0.0.2"}]}, + {release, {foo, "0.0.3"}, + [sasl, + {goal_app_1, "0.0.3"}, + {goal_app_2, "0.0.3"}]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, State} = relcool:do([{relname, foo}, + {relvsn, "0.0.3"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 2}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["relup"]), + + %% we should have one 'resolved' release and three discovered realized_releases. + ?assertMatch([{foo, "0.0.1"}, + {foo, "0.0.2"}, + {foo, "0.0.3"}], + lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), + ?assert(rcl_release:realized(Release)), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rcl_state:realized_releases(State)))), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rcl_state:realized_releases(State)))), + + ?assertMatch({ok, [{"0.0.3", + [{"0.0.2",[],[point_of_no_return]}], + [{"0.0.2",[],[point_of_no_return]}]}]}, + file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), + filename:basename(rcl_release:relfile(Release), ".rel") ++ + ".relup"))), + + ?assertMatch(foo, rcl_release:name(Release)), + ?assertMatch("0.0.3", rcl_release:vsn(Release)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.3"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.3"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + + +make_relup_release2(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + write_appup_file(GA1, "0.0.1"), + write_appup_file(GA2, "0.0.1"), + + ConfigFile = filename:join([LibDir1, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [sasl, + {goal_app_1, "0.0.1"}, + {goal_app_2, "0.0.1"}]}, + {release, {foo, "0.0.2"}, + [sasl, + {goal_app_1, "0.0.2"}, + {goal_app_2, "0.0.2"}]}, + {release, {foo, "0.0.3"}, + [sasl, + {goal_app_1, "0.0.3"}, + {goal_app_2, "0.0.3"}]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + create_random_name("relcool-output")]), + {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, + OutputDir, ConfigFile), + {ok, State} = relcool:do([{relname, foo}, + {relvsn, "0.0.3"}, + {upfrom, "0.0.1"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 2}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["relup"]), + + %% we should have one 'resolved' release and three discovered realized_releases. + ?assertMatch([{foo, "0.0.1"}, + {foo, "0.0.2"}, + {foo, "0.0.3"}], + lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), + ?assert(rcl_release:realized(Release)), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rcl_state:realized_releases(State)))), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rcl_state:realized_releases(State)))), + + ?assertMatch({ok, [{"0.0.3", + [{"0.0.1",[],[point_of_no_return]}], + [{"0.0.1",[],[point_of_no_return]}]}]}, + file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), + filename:basename(rcl_release:relfile(Release), ".rel") ++ + ".relup"))), + + ?assertMatch(foo, rcl_release:name(Release)), + ?assertMatch("0.0.3", rcl_release:vsn(Release)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.3"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.3"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + + +make_one_app_top_level_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + {ok, AppInfo} = create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel], []), + AppDir = rcl_app_info:dir(AppInfo), + ConfigFile = filename:join([AppDir, "relcool.config"]), + write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [{goal_app_1, "0.0.1"}]}]), + + OutputDir = filename:join([AppDir, + create_random_name("relcool-output")]), + + {ok, Cwd} = file:get_cwd(), + ok = file:set_cwd(AppDir), + {ok, State} = relcool:do(undefined, undefined, [], [], 2, + OutputDir, ConfigFile), + ok = file:set_cwd(Cwd), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + AppSpecs = rcl_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)). + + +%%%=================================================================== +%%% Helper Functions +%%%=================================================================== + +create_app(Dir, Name, Vsn, Deps, LibDeps) -> + AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), + write_app_file(AppDir, Name, Vsn, Deps, LibDeps), + write_beam_file(AppDir, Name), + rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + Deps, []). + +create_empty_app(Dir, Name, Vsn, Deps, LibDeps) -> + AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), + write_app_file(AppDir, Name, Vsn, Deps, LibDeps), + rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + Deps, []). + +write_beam_file(Dir, Name) -> + Beam = filename:join([Dir, "ebin", "not_a_real_beam" ++ Name ++ ".beam"]), + ok = filelib:ensure_dir(Beam), + ok = ec_file:write_term(Beam, testing_purposes_only). + +write_appup_file(AppInfo, DownVsn) -> + Dir = rcl_app_info:dir(AppInfo), + Name = rcl_util:to_string(rcl_app_info:name(AppInfo)), + Vsn = rcl_app_info:vsn_as_string(AppInfo), + Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). + +write_app_file(Dir, Name, Version, Deps, LibDeps) -> + Filename = filename:join([Dir, "ebin", Name ++ ".app"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_app_metadata(Name, Version, Deps, LibDeps)). + +get_app_metadata(Name, Vsn, Deps, LibDeps) -> + {application, erlang:list_to_atom(Name), + [{description, ""}, + {vsn, Vsn}, + {modules, []}, + {included_applications, LibDeps}, + {registered, []}, + {applications, Deps}]}. + +create_random_name(Name) -> + random:seed(erlang:now()), + Name ++ erlang:integer_to_list(random:uniform(1000000)). + +create_random_vsn() -> + random:seed(erlang:now()), + lists:flatten([erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100))]). + +write_config(Filename, Values) -> + ok = ec_file:write(Filename, + [io_lib:format("~p.\n", [Val]) || Val <- Values]). + +test_template_contents() -> + "{erts_vsn, \"{{erts_vsn}}\"}.\n" + "{release_erts_version, \"{{release_erts_version}}\"}.\n" + "{release_name, {{release_name}}}.\n" + "{rel_vsn, \"{{release_version}}\"}.\n" + "{release_version, \"{{release_version}}\"}.\n" + "{release_applications, [{{ release_applications|join:\", \" }}]}.\n" + "{std_version, \"{{release.stdlib.version}}\"}.\n" + "{kernel_version, \"{{release.kernel.version}}\"}.\n" + "{non_goal_1_version, \"{{release.non_goal_1.version}}\"}.\n" + "{non_goal_2_version, \"{{release.non_goal_2.version}}\"}.\n" + "{goal_app_1_version, \"{{release.goal_app_1.version}}\"}.\n" + "{goal_app_2_version, \"{{release.goal_app_2.version}}\"}.\n" + "{lib_dep_1, \"{{release.lib_dep_1.version}}\"}.\n" + "{lib_dep_1_dir, \"{{release.lib_dep_1.dir}}\"}.\n" + "{lib_dep_1_active, [{{ release.lib_dep_1.active_dependencies|join:\", \" }}]}.\n" + "{lib_dep_1_library, [{{ release.lib_dep_1.library_dependencies|join:\", \" }}]}.\n" + "{lib_dep_1_link, \"{{release.lib_dep_1.link}}\"}.\n" + "{log, \"{{log}}\"}.\n" + "{output_dir, \"{{output_dir}}\"}.\n" + "{target_dir, \"{{target_dir}}\"}.\n" + "{overridden, [{{ overridden|join:\", \" }}]}.\n" + "{goals, [\"{{ goals|join:\", \" }}\"]}.\n" + "{lib_dirs, [\"{{ lib_dirs|join:\", \" }}\"]}.\n" + "{config_file, \"{{ config_file }}\"}.\n" + "{providers, [{{ providers|join:\", \" }}]}.\n" + "{sys_config, \"{{sys_config}}\"}.\n" + "{root_dir, \"{{root_dir}}\"}.\n" + "{default_release_name, {{default_release_name}}}.\n" + "{default_release_version, \"{{default_release_version}}\"}.\n" + "{default_release, \"{{default_release}}\"}.\n" + "{yahoo, \"{{yahoo}}\"}.\n" + "{yahoo2_foo, \"{{yahoo2.foo}}\"}.\n" + "{foo_dir, \"{{foo_dir}}\"}.\n" + "{yahoo3, \"{{yahoo3.bar}}\"}.\n". -- cgit v1.2.3 From c5f0a8c9175b2d152c69f72da15e7ceff411f86b Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 May 2013 17:13:06 -0700 Subject: internal rename completion of all relcool to relx calls --- .gitignore | 4 +- Makefile | 2 +- README.md | 16 ++-- include/relx.hrl | 11 +-- relx.config | 4 +- src/relx.app.src | 2 +- src/relx.erl | 92 ++++++++++---------- src/rlx_app_discovery.erl | 44 +++++----- src/rlx_app_info.erl | 26 +++--- src/rlx_cmd_args.erl | 94 ++++++++++---------- src/rlx_depsolver.erl | 36 ++++---- src/rlx_depsolver_culprit.erl | 96 ++++++++++----------- src/rlx_dscv_util.erl | 6 +- src/rlx_goal.erl | 10 +-- src/rlx_goal.peg | 10 +-- src/rlx_goal_utils.erl | 6 +- src/rlx_log.erl | 48 +++++------ src/rlx_provider.erl | 18 ++-- src/rlx_prv_assembler.erl | 162 +++++++++++++++++------------------ src/rlx_prv_config.erl | 64 +++++++------- src/rlx_prv_discover.erl | 42 ++++----- src/rlx_prv_overlay.erl | 182 +++++++++++++++++++-------------------- src/rlx_prv_release.erl | 84 +++++++++--------- src/rlx_rel_discovery.erl | 44 +++++----- src/rlx_release.erl | 94 ++++++++++---------- src/rlx_state.erl | 96 ++++++++++----------- src/rlx_topo.erl | 58 ++++++------- src/rlx_util.erl | 10 +-- test/rlx_command_SUITE.erl | 30 +++---- test/rlx_depsolver_tester.erl | 18 ++-- test/rlx_depsolver_tests.erl | 106 +++++++++++------------ test/rlx_discover_SUITE.erl | 30 +++---- test/rlx_goal_tests.erl | 38 ++++----- test/rlx_release_SUITE.erl | 194 +++++++++++++++++++++--------------------- 34 files changed, 889 insertions(+), 888 deletions(-) diff --git a/.gitignore b/.gitignore index 424df1e..666c837 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ deps/* ebin/* *.beam .eunit -.relcool_plt -relcool +.relx_plt +relx # This is a generated file that should be ignored src/rcl_goal.erl logs diff --git a/Makefile b/Makefile index fbdd672..bfbaab8 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ ct: compile clean-common-test-data -pa $(CURDIR)/deps/*/ebin \ -logdir $(CURDIR)/logs \ -dir $(CURDIR)/test/ \ - -suite rclt_command_SUITE rclt_discover_SUITE -suite rclt_release_SUITE + -suite rlx_command_SUITE rlx_discover_SUITE -suite rlx_release_SUITE test: compile dialyzer eunit ct diff --git a/README.md b/README.md index b235f37..e7d0655 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -[![Build Status](https://travis-ci.org/erlware/relcool.png)](https://travis-ci.org/erlware/relcool) +[![Build Status](https://travis-ci.org/erlware/relx.png)](https://travis-ci.org/erlware/relx) # NAME -relcool - A release assembler for erlang +relx - A release assembler for erlang # SYNOPSIS -relcool [*options*] [*release-specification-file*] +relx [*options*] [*release-specification-file*] # DESCRIPTION @@ -16,18 +16,18 @@ applications it will generate a release output. That output depends heavily on what plugins available and what options are defined, but usually it is simple a well configured release directory. - relcool -c relcool.config -l ~/my-dirs --relname foo --relvsn 0.0.1 --target-spec myapp --target-spec getopt>=0.5.1 -o output-dir + relx -c relx.config -l ~/my-dirs --relname foo --relvsn 0.0.1 --target-spec myapp --target-spec getopt>=0.5.1 -o output-dir The *release-specification-file* is optional but otherwise contains additional specification information for releases. # BUILDING -To build relcool and generate a standalone escript executable: +To build relx and generate a standalone escript executable: $ make -This creates the executable `relcool`. +This creates the executable `relx`. # OPTIONS @@ -54,7 +54,7 @@ This creates the executable `relcool`. : The verbosity level of the system. Valid values are 1 - 3 -c *INTEGER*, \--config *INTEGER* -: The custom config file for the relcool system +: The custom config file for the relx system # CONFIGURATION FILES @@ -64,4 +64,4 @@ Configuration files `reltool` (1). -[relcool wiki](https://github.com/erlware/relcool/wiki) +[relx wiki](https://github.com/erlware/relx/wiki) diff --git a/include/relx.hrl b/include/relx.hrl index 11a0dec..4b78ca6 100644 --- a/include/relx.hrl +++ b/include/relx.hrl @@ -1,3 +1,4 @@ + %% Copyright 2012 Erlware, LLC. All Rights Reserved. %% %% This file is provided to you under the Apache License, @@ -15,13 +16,13 @@ %% under the License. %% --define(RCL_ERROR, 0). --define(RCL_INFO, 1). --define(RCL_DEBUG, 2). +-define(RLX_ERROR, 0). +-define(RLX_INFO, 1). +-define(RLX_DEBUG, 2). -%% This is the default form of error messages for the Relcool +%% This is the default form of error messages for the Relx %% system. It is expected that everything that returns an error use %% this and that they all expose a format_error/1 message that returns %% an iolist. --define(RCL_ERROR(Reason), +-define(RLX_ERROR(Reason), {error, {?MODULE, Reason}}). diff --git a/relx.config b/relx.config index a86ab3f..7a3d62e 100644 --- a/relx.config +++ b/relx.config @@ -1,3 +1,3 @@ %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- -{release, {relcool, "0.0.1"}, - [relcool]}. +{release, {relx, "0.0.1"}, + [relx]}. diff --git a/src/relx.app.src b/src/relx.app.src index 0c1c45a..3f655cf 100644 --- a/src/relx.app.src +++ b/src/relx.app.src @@ -18,7 +18,7 @@ %% under the License. %% -{application, relcool, +{application, relx, [{description, "Release assembler for Erlang/OTP Releases"}, {vsn, "0.0.5"}, {modules, []}, diff --git a/src/relx.erl b/src/relx.erl index c404e6f..30ee77f 100644 --- a/src/relx.erl +++ b/src/relx.erl @@ -18,7 +18,7 @@ %%% @author Eric Merritt %%% @copyright (C) 2012 Erlware, LLC. %%% @doc --module(relcool). +-module(relx). -export([main/1, do/2, @@ -30,82 +30,82 @@ -export_type([error/0]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% types %%============================================================================ -type error() :: {error, {Module::module(), Reason::term()}}. --type goal() :: string() | binary() | rcl_depsolver:constraint(). +-type goal() :: string() | binary() | rlx_depsolver:constraint(). %%============================================================================ %% API %%============================================================================ --spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. +-spec main([string()]) -> ok | error() | {ok, rlx_state:t()}. main(Args) -> OptSpecList = opt_spec_list(), Result = case getopt:parse(OptSpecList, Args) of {ok, {Options, NonOptions}} -> do([{caller, command_line} | Options], NonOptions); {error, Detail} -> - ?RCL_ERROR({opt_parse, Detail}) + ?RLX_ERROR({opt_parse, Detail}) end, case Result of {error, _} -> - report_error(rcl_state:caller(rcl_state:new([], undefined), + report_error(rlx_state:caller(rlx_state:new([], undefined), command_line), Result); _ -> Result end. -%% @doc provides an API to run the Relcool process from erlang applications +%% @doc provides an API to run the Relx process from erlang applications %% %% @param RelName - The release name to build (maybe `undefined`) %% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% @param Goals - The release goals for the system in depsolver or Relx goal %% format %% @param LibDirs - The library dirs that should be used for the system %% @param OutputDir - The directory where the release should be built to %% @param Configs - The list of config files for the system --spec do(atom(), string(), [goal()], [file:name()], rcl_log:log_level(), +-spec do(atom(), string(), [goal()], [file:name()], rlx_log:log_level(), [file:name()], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. + ok | error() | {ok, rlx_state:t()}. do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Config) -> {ok, Cwd} = file:get_cwd(), do(Cwd, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Config). -%% @doc provides an API to run the Relcool process from erlang applications +%% @doc provides an API to run the Relx process from erlang applications %% %% @param RootDir - The root directory for the project %% @param RelName - The release name to build (maybe `undefined`) %% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% @param Goals - The release goals for the system in depsolver or Relx goal %% format %% @param LibDirs - The library dirs that should be used for the system %% @param OutputDir - The directory where the release should be built to %% @param Configs - The list of config files for the system -spec do(file:name(), atom(), string(), [goal()], [file:name()], - rcl_log:log_level(), [file:name()], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. + rlx_log:log_level(), [file:name()], file:name() | undefined) -> + ok | error() | {ok, rlx_state:t()}. do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Configs). -%% @doc provides an API to run the Relcool process from erlang applications +%% @doc provides an API to run the Relx process from erlang applications %% %% @param RootDir - The root directory for the system %% @param RelName - The release name to build (maybe `undefined`) %% @param RelVsn - The release version to build (maybe `undefined`) -%% @param Goals - The release goals for the system in depsolver or Relcool goal +%% @param Goals - The release goals for the system in depsolver or Relx goal %% format %% @param LibDirs - The library dirs that should be used for the system %% @param OutputDir - The directory where the release should be built to %% @param Overrides - A list of overrides for the system %% @param Configs - The list of config files for the system -spec do(file:name(), atom(), string(), [goal()], [file:name()], - rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> - ok | error() | {ok, rcl_state:t()}. + rlx_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> + ok | error() | {ok, rlx_state:t()}. do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> do([{relname, RelName}, {relvsn, RelVsn}, @@ -118,7 +118,7 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con {config, Config}], ["release"]). -%% @doc provides an API to run the Relcool process from erlang applications +%% @doc provides an API to run the Relx process from erlang applications %% %% @param Opts - A proplist of options. There are good defaults for each of %% these entries, so any or all may be omitted. Individual options may be: @@ -129,7 +129,7 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con %%
{relvsn, RelVsn}
%%
The release version to build
%%
{goals, Goals}
-%%
The release goals for the system in depsolver or Relcool goal +%%
The release goals for the system in depsolver or Relx goal %% format (@see goals())
%%
{lib_dirs, LibDirs}
%%
A list of library dirs that should be used for the system
@@ -139,9 +139,9 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con %%
{output_dir, OutputDir}
%%
The directory where the release should be built to
%%
{root_dir, RootDir}
-%%
The base directory for this run of relcool.
+%%
The base directory for this run of relx.
%%
{config, Config}
-%%
The path to a relcool config file
+%%
The path to a relx config file
%%
{log_level, LogLevel}
%%
Defines the verbosity of output. Maybe a number between 0 and 2, with %% with higher values being more verbose
@@ -154,11 +154,11 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con %% Overrides %% -spec do(proplists:proplist(), [string()]) -> - ok | error() | {ok, rcl_state:t()}. + ok | error() | {ok, rlx_state:t()}. do(Opts, NonOpts) -> - case rcl_cmd_args:args2state(Opts, NonOpts) of + case rlx_cmd_args:args2state(Opts, NonOpts) of {ok, State} -> - run_relcool_process(State); + run_relx_process(State); Error={error, _} -> Error end. @@ -188,7 +188,7 @@ opt_spec_list() -> -spec format_error(Reason::term()) -> iolist(). format_error({invalid_return_value, Provider, Value}) -> - [rcl_provider:format(Provider), " returned an invalid value ", + [rlx_provider:format(Provider), " returned an invalid value ", io_lib:format("~p", [Value])]; format_error({opt_parse, {invalid_option, Opt}}) -> io_lib:format("invalid option ~s~n", [Opt]); @@ -200,11 +200,11 @@ format_error({error, {Module, Reason}}) -> %%============================================================================ %% internal api %%============================================================================ -run_relcool_process(State) -> - rcl_log:info(rcl_state:log(State), "Starting relcool build process ..."), - rcl_log:debug(rcl_state:log(State), +run_relx_process(State) -> + rlx_log:info(rlx_state:log(State), "Starting relx build process ..."), + rlx_log:debug(rlx_state:log(State), fun() -> - rcl_state:format(State) + rlx_state:format(State) end), run_providers(State). @@ -214,16 +214,16 @@ run_relcool_process(State) -> %% providers again and run the rest of them (because they could have been %% updated by the config process). run_providers(State0) -> - [ConfigProvider | _] = rcl_state:providers(State0), + [ConfigProvider | _] = rlx_state:providers(State0), case run_provider(ConfigProvider, {ok, State0}) of Err = {error, _} -> Err; {ok, State1} -> - RootDir = rcl_state:root_dir(State1), + RootDir = rlx_state:root_dir(State1), ok = file:set_cwd(RootDir), - Providers = rcl_state:providers(State1), + Providers = rlx_state:providers(State1), Result = run_providers(ConfigProvider, Providers, State1), - handle_output(State1, rcl_state:caller(State1), Result) + handle_output(State1, rlx_state:caller(State1), Result) end. handle_output(State, command_line, E={error, _}) -> @@ -244,33 +244,33 @@ run_providers(ConfigProvider, Providers, State0) -> lists:foldl(fun run_provider/2, {ok, State0}, Providers) end. --spec run_provider(rcl_provider:t(), {ok, rcl_state:t()} | error()) -> - {ok, rcl_state:t()} | error(). +-spec run_provider(rlx_provider:t(), {ok, rlx_state:t()} | error()) -> + {ok, rlx_state:t()} | error(). run_provider(_Provider, Error = {error, _}) -> Error; run_provider(Provider, {ok, State0}) -> - rcl_log:debug(rcl_state:log(State0), "Running provider ~p~n", - [rcl_provider:impl(Provider)]), - case rcl_provider:do(Provider, State0) of + rlx_log:debug(rlx_state:log(State0), "Running provider ~p~n", + [rlx_provider:impl(Provider)]), + case rlx_provider:do(Provider, State0) of {ok, State1} -> - rcl_log:debug(rcl_state:log(State0), "Provider successfully run: ~p~n", - [rcl_provider:impl(Provider)]), + rlx_log:debug(rlx_state:log(State0), "Provider successfully run: ~p~n", + [rlx_provider:impl(Provider)]), {ok, State1}; E={error, _} -> - rcl_log:debug(rcl_state:log(State0), "Provider (~p) failed with: ~p~n", - [rcl_provider:impl(Provider), E]), + rlx_log:debug(rlx_state:log(State0), "Provider (~p) failed with: ~p~n", + [rlx_provider:impl(Provider), E]), E end. -spec usage() -> ok. usage() -> - getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]"). + getopt:usage(opt_spec_list(), "relx", "[*release-specification-file*]"). --spec report_error(rcl_state:t(), error()) -> none() | error(). +-spec report_error(rlx_state:t(), error()) -> none() | error(). report_error(State, Error) -> io:format(format_error(Error)), usage(), - case rcl_state:caller(State) of + case rlx_state:caller(State) of command_line -> erlang:halt(127); api -> diff --git a/src/rlx_app_discovery.erl b/src/rlx_app_discovery.erl index 7b04b19..e6b6e2c 100644 --- a/src/rlx_app_discovery.erl +++ b/src/rlx_app_discovery.erl @@ -21,13 +21,13 @@ %%% @doc This provider uses the lib_dir setting of the state. It searches the %%% Lib Dirs looking for all OTP Applications that are available. When it finds %%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_app_discovery). +%%% the state of available apps. This implements the rlx_provider behaviour. +-module(rlx_app_discovery). -export([do/2, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API @@ -35,12 +35,12 @@ %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t(), [filename:name()]) -> {ok, [rcl_app_info:t()]} | relcool:error(). +-spec do(rlx_state:t(), [filename:name()]) -> {ok, [rlx_app_info:t()]} | relx:error(). do(State, LibDirs) -> - rcl_log:info(rcl_state:log(State), + rlx_log:info(rlx_state:log(State), fun() -> ["Resolving OTP Applications from directories:\n", - [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] + [[rlx_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] end), resolve_app_metadata(State, LibDirs). @@ -53,7 +53,7 @@ format_error(ErrorDetails) %%% Internal Functions %%%=================================================================== resolve_app_metadata(State, LibDirs) -> - AppMeta0 = lists:flatten(rcl_dscv_util:do(fun discover_dir/2, LibDirs)), + AppMeta0 = lists:flatten(rlx_dscv_util:do(fun discover_dir/2, LibDirs)), case [case Err of {error, Ret} -> Ret @@ -66,26 +66,26 @@ resolve_app_metadata(State, LibDirs) -> false end] of [] -> - SkipApps = rcl_state:skip_apps(State), + SkipApps = rlx_state:skip_apps(State), AppMeta1 = [App || {ok, App} <- setup_overrides(State, AppMeta0), - not lists:keymember(rcl_app_info:name(App), 1, SkipApps)], - rcl_log:debug(rcl_state:log(State), + not lists:keymember(rlx_app_info:name(App), 1, SkipApps)], + rlx_log:debug(rlx_state:log(State), fun() -> ["Resolved the following OTP Applications from the system: \n", - [[rcl_app_info:format(1, App), "\n"] || App <- AppMeta1]] + [[rlx_app_info:format(1, App), "\n"] || App <- AppMeta1]] end), {ok, AppMeta1}; Errors -> - ?RCL_ERROR(Errors) + ?RLX_ERROR(Errors) end. app_name({error, _}) -> undefined; app_name({ok, AppMeta}) -> - rcl_app_info:name(AppMeta). + rlx_app_info:name(AppMeta). setup_overrides(State, AppMetas0) -> - Overrides = rcl_state:overrides(State), + Overrides = rlx_state:overrides(State), AppMetas1 = [AppMeta || AppMeta <- AppMetas0, not lists:keymember(app_name(AppMeta), 1, Overrides)], [case is_valid_otp_app(filename:join([FileName, <<"ebin">>, @@ -95,7 +95,7 @@ setup_overrides(State, AppMetas0) -> Error = {error, _} -> Error; {ok, App} -> - {ok, rcl_app_info:link(App, true)} + {ok, rlx_app_info:link(App, true)} end || {AppName, FileName} <- Overrides] ++ AppMetas1. @@ -123,13 +123,13 @@ format_detail({app_info_error, {Module, Detail}}) -> Module:format_error(Detail). -spec discover_dir([file:name()], directory | file) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. + {ok, rlx_app_info:t()} | {error, Reason::term()}. discover_dir(_File, directory) -> {noresult, true}; discover_dir(File, file) -> is_valid_otp_app(File). --spec is_valid_otp_app(file:name()) -> {ok, rcl_app_info:t()} | {error, Reason::term()} | +-spec is_valid_otp_app(file:name()) -> {ok, rlx_app_info:t()} | {error, Reason::term()} | {noresult, false}. is_valid_otp_app(File) -> @@ -148,7 +148,7 @@ is_valid_otp_app(File) -> end. -spec has_at_least_one_beam(file:name(), file:filename()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. + {ok, rlx_app_info:t()} | {error, Reason::term()}. has_at_least_one_beam(EbinDir, File) -> case file:list_dir(EbinDir) of {ok, List} -> @@ -163,7 +163,7 @@ has_at_least_one_beam(EbinDir, File) -> end. -spec gather_application_info(file:name(), file:filename()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. + {ok, rlx_app_info:t()} | {error, Reason::term()}. gather_application_info(EbinDir, File) -> AppDir = filename:dirname(EbinDir), case file:consult(File) of @@ -176,7 +176,7 @@ gather_application_info(EbinDir, File) -> end. -spec get_vsn(file:name(), atom(), proplists:proplist()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. + {ok, rlx_app_info:t()} | {error, Reason::term()}. get_vsn(AppDir, AppName, AppDetail) -> case proplists:get_value(vsn, AppDetail) of undefined -> @@ -191,11 +191,11 @@ get_vsn(AppDir, AppName, AppDetail) -> end. -spec get_deps(file:name(), atom(), string(), proplists:proplist()) -> - {ok, rcl_app_info:t()} | {error, Reason::term()}. + {ok, rlx_app_info:t()} | {error, Reason::term()}. get_deps(AppDir, AppName, AppVsn, AppDetail) -> ActiveApps = proplists:get_value(applications, AppDetail, []), LibraryApps = proplists:get_value(included_applications, AppDetail, []), - rcl_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps). + rlx_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps). %%%=================================================================== %%% Test Functions diff --git a/src/rlx_app_info.erl b/src/rlx_app_info.erl index dadc579..de6ed7d 100644 --- a/src/rlx_app_info.erl +++ b/src/rlx_app_info.erl @@ -34,7 +34,7 @@ %%% application metadata. %%% %%% --module(rcl_app_info). +-module(rlx_app_info). -export([new/0, new/5, @@ -57,7 +57,7 @@ -export_type([t/0]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). -record(app_info_t, {name :: atom(), vsn :: ec_semver:semver(), @@ -82,20 +82,20 @@ new() -> %% @doc build a complete version of the app info with all fields set. -spec new(atom(), string(), file:name(), [atom()], [atom()]) -> - {ok, t()} | relcool:error(). + {ok, t()} | relx:error(). new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) -> new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, false). %% @doc build a complete version of the app info with all fields set. -spec new(atom(), string(), file:name(), [atom()], [atom()], boolean()) -> - {ok, t()} | relcool:error(). + {ok, t()} | relx:error(). new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, Link) when erlang:is_atom(AppName), erlang:is_list(ActiveDeps), erlang:is_list(LibraryDeps) -> case parse_version(Vsn) of {fail, _} -> - ?RCL_ERROR({vsn_parse, AppName}); + ?RLX_ERROR({vsn_parse, AppName}); ParsedVsn -> {ok, #app_info_t{name=AppName, vsn=ParsedVsn, dir=Dir, active_deps=ActiveDeps, @@ -120,12 +120,12 @@ vsn(#app_info_t{vsn=Vsn}) -> vsn_as_string(#app_info_t{vsn=Vsn}) -> erlang:binary_to_list(erlang:iolist_to_binary(ec_semver:format(Vsn))). --spec vsn(t(), string()) -> {ok, t()} | relcool:error(). +-spec vsn(t(), string()) -> {ok, t()} | relx:error(). vsn(AppInfo=#app_info_t{name=AppName}, AppVsn) when erlang:is_list(AppVsn) -> case parse_version(AppVsn) of {fail, _} -> - ?RCL_ERROR({vsn_parse, AppName}); + ?RLX_ERROR({vsn_parse, AppName}); ParsedVsn -> {ok, AppInfo#app_info_t{vsn=ParsedVsn}} end. @@ -175,13 +175,13 @@ format(AppInfo) -> format(Indent, #app_info_t{name=Name, vsn=Vsn, dir=Dir, active_deps=Deps, library_deps=LibDeps, link=Link}) -> - [rcl_util:indent(Indent), erlang:atom_to_list(Name), "-", ec_semver:format(Vsn), + [rlx_util:indent(Indent), erlang:atom_to_list(Name), "-", ec_semver:format(Vsn), ": ", Dir, "\n", - rcl_util:indent(Indent + 1), "Symlink: ", erlang:atom_to_list(Link), "\n", - rcl_util:indent(Indent + 1), "Active Dependencies:\n", - [[rcl_util:indent(Indent + 2), erlang:atom_to_list(Dep), ",\n"] || Dep <- Deps], - rcl_util:indent(Indent + 1), "Library Dependencies:\n", - [[rcl_util:indent(Indent + 2), erlang:atom_to_list(LibDep), ",\n"] || LibDep <- LibDeps]]. + rlx_util:indent(Indent + 1), "Symlink: ", erlang:atom_to_list(Link), "\n", + rlx_util:indent(Indent + 1), "Active Dependencies:\n", + [[rlx_util:indent(Indent + 2), erlang:atom_to_list(Dep), ",\n"] || Dep <- Deps], + rlx_util:indent(Indent + 1), "Library Dependencies:\n", + [[rlx_util:indent(Indent + 2), erlang:atom_to_list(LibDep), ",\n"] || LibDep <- LibDeps]]. %%%=================================================================== %%% Internal Functions diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl index fee4449..b5fe8a7 100644 --- a/src/rlx_cmd_args.erl +++ b/src/rlx_cmd_args.erl @@ -19,19 +19,19 @@ %%% @copyright (C) 2012 Erlware, LLC. %%% %%% @doc Trivial utility file to help handle common tasks --module(rcl_cmd_args). +-module(rlx_cmd_args). -export([args2state/2, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API %%============================================================================ -spec args2state([getopt:option()], [string()]) -> - {ok, {rcl_state:t(), [string()]}} | - relcool:error(). + {ok, {rlx_state:t(), [string()]}} | + relx:error(). args2state(Opts, Target) when erlang:length(Target) == 0; erlang:length(Target) == 1 -> RelName = proplists:get_value(relname, Opts, undefined), @@ -49,7 +49,7 @@ args2state(Opts, Target) Error end; args2state(_Opts, Targets) -> - ?RCL_ERROR({invalid_targets, Targets}). + ?RLX_ERROR({invalid_targets, Targets}). -spec format_error(Reason::term()) -> iolist(). format_error({invalid_targets, Targets}) -> @@ -89,17 +89,17 @@ format_error({invalid_target, Target}) -> %%% Internal Functions %%%=================================================================== -spec handle_config([getopt:option()], atom(), proplists:proplist()) -> - {ok, {rcl_state:t(), [string()]}} | - relcool:error(). + {ok, {rlx_state:t(), [string()]}} | + relx:error(). handle_config(Opts, Target, CommandLineConfig) -> case validate_config(proplists:get_value(config, Opts, [])) of Error = {error, _} -> Error; {ok, Config} -> - {ok, rcl_state:new([{config, Config} | CommandLineConfig], Target)} + {ok, rlx_state:new([{config, Config} | CommandLineConfig], Target)} end. --spec convert_target([string()]) -> {ok, release | relup} | relcool:error(). +-spec convert_target([string()]) -> {ok, release | relup} | relx:error(). convert_target([]) -> {ok, release}; convert_target(["release"]) -> @@ -107,10 +107,10 @@ convert_target(["release"]) -> convert_target(["relup"]) -> {ok, relup}; convert_target(Target) -> - ?RCL_ERROR({invalid_target, Target}). + ?RLX_ERROR({invalid_target, Target}). -spec validate_config(file:filename() | undefined) -> - {ok, file:filename() | undefined} | relcool:error(). + {ok, file:filename() | undefined} | relx:error(). validate_config(undefined) -> {ok, undefined}; validate_config("") -> @@ -120,22 +120,22 @@ validate_config(Config) -> true -> {ok, filename:absname(Config)}; false -> - ?RCL_ERROR({invalid_config_file, Config}) + ?RLX_ERROR({invalid_config_file, Config}) end. --spec create_log([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_log([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_log(Opts, Acc) -> LogLevel = proplists:get_value(log_level, Opts, 0), if LogLevel >= 0, LogLevel =< 2 -> - create_goals(Opts, [{log, rcl_log:new(LogLevel)} | Acc]); + create_goals(Opts, [{log, rlx_log:new(LogLevel)} | Acc]); true -> - ?RCL_ERROR({invalid_log_level, LogLevel}) + ?RLX_ERROR({invalid_log_level, LogLevel}) end. --spec create_goals([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_goals([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_goals(Opts, Acc) -> Goals = proplists:get_value(goals, Opts, []) ++ proplists:get_all_values(goal, Opts), @@ -146,8 +146,8 @@ create_goals(Opts, Acc) -> create_overrides(Opts, [{goals, Specs} | Acc]) end. --spec create_overrides([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_overrides([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_overrides(Opts, Acc) -> Overrides = proplists:get_all_values(override, Opts) ++ proplists:get_value(overrides, Opts, []), @@ -160,7 +160,7 @@ create_overrides(Opts, Acc) -> -spec convert_overrides([{atom(), string() | binary()} | string() | binary()], [{atom(), string() | binary()}]) -> - {ok, [string() | binary()]} | relcool:error(). + {ok, [string() | binary()]} | relx:error(). convert_overrides([], Acc) -> {ok, Acc}; convert_overrides([QA = {OverrideApp, _} | Rest], Acc) @@ -172,24 +172,24 @@ convert_overrides([Override | Rest], Acc) [AppName, AppDir] -> convert_overrides(Rest, [{erlang:iolist_to_binary(AppName), AppDir} | Acc]); _ -> - ?RCL_ERROR({failed_to_parse_override, Override}) + ?RLX_ERROR({failed_to_parse_override, Override}) end; convert_overrides([QA | _], _) -> - ?RCL_ERROR({failed_to_parse_override, QA}). + ?RLX_ERROR({failed_to_parse_override, QA}). --spec convert_goals([string()], [rcl_depsolver:constraint()]) -> - {ok,[rcl_depsolver:constraint()]} | - relcool:error(). +-spec convert_goals([string()], [rlx_depsolver:constraint()]) -> + {ok,[rlx_depsolver:constraint()]} | + relx:error(). convert_goals([], Specs) -> - %% Reverse the specs because order matters to rcl_depsolver + %% Reverse the specs because order matters to rlx_depsolver {ok, lists:reverse(Specs)}; convert_goals([RawSpec | Rest], Acc) -> parse_goal(RawSpec, Rest, Acc). --spec parse_goal(string() | binary() | rcl_depsolver:constraint(), - [string() | binary() | rcl_depsolver:constraint()], - rcl_depsolver:constraints()) -> - {ok, rcl_depsolver:constraints()} | relcool:error(). +-spec parse_goal(string() | binary() | rlx_depsolver:constraint(), + [string() | binary() | rlx_depsolver:constraint()], + rlx_depsolver:constraints()) -> + {ok, rlx_depsolver:constraints()} | relx:error(). parse_goal(Spec, Rest, Acc) when erlang:is_atom(Spec) -> convert_goals(Rest, [Spec | Acc]); @@ -197,21 +197,21 @@ parse_goal(Spec, Rest, Acc) when erlang:is_tuple(Spec) -> convert_goals(Rest, [Spec | Acc]); parse_goal(RawSpec, Rest, Acc) -> - case rcl_goal:parse(RawSpec) of + case rlx_goal:parse(RawSpec) of {ok, Spec} -> convert_goals(Rest, [Spec | Acc]); {fail, _} -> - ?RCL_ERROR({failed_to_parse, RawSpec}) + ?RLX_ERROR({failed_to_parse, RawSpec}) end. --spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_output_dir([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_output_dir(Opts, Acc) -> OutputDir = proplists:get_value(output_dir, Opts, "./_rel"), create_lib_dirs(Opts, [{output_dir, filename:absname(OutputDir)} | Acc]). --spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_lib_dirs([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_lib_dirs(Opts, Acc) -> Dirs = proplists:get_all_values(lib_dir, Opts) ++ proplists:get_value(lib_dirs, Opts, []), @@ -222,8 +222,8 @@ create_lib_dirs(Opts, Acc) -> create_root_dir(Opts, [{lib_dirs, [filename:absname(Dir) || Dir <- Dirs]} | Acc]) end. --spec create_root_dir([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_root_dir([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_root_dir(Opts, Acc) -> Dir = proplists:get_value(root_dir, Opts, undefined), case Dir of @@ -234,14 +234,14 @@ create_root_dir(Opts, Acc) -> create_disable_default_libs(Opts, [{root_dir, Dir} | Acc]) end. --spec create_disable_default_libs([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_disable_default_libs([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_disable_default_libs(Opts, Acc) -> Def = proplists:get_value(disable_default_libs, Opts, false), create_upfrom(Opts, [{disable_default_libs, Def} | Acc]). -spec create_upfrom([getopt:option()], rcl:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). + {ok, rlx_state:cmd_args()} | relx:error(). create_upfrom(Opts, Acc) -> case proplists:get_value(upfrom, Opts, undefined) of undefined -> @@ -250,8 +250,8 @@ create_upfrom(Opts, Acc) -> create_caller(Opts, [{upfrom, UpFrom} | Acc]) end. --spec create_caller([getopt:option()], rcl_state:cmd_args()) -> - {ok, rcl_state:cmd_args()} | relcool:error(). +-spec create_caller([getopt:option()], rlx_state:cmd_args()) -> + {ok, rlx_state:cmd_args()} | relx:error(). create_caller(Opts, Acc) -> case proplists:get_value(caller, Opts, api) of "command_line" -> @@ -267,15 +267,15 @@ create_caller(Opts, Acc) -> command_line -> {ok, [{caller, command_line} | Acc]}; Caller -> - ?RCL_ERROR({invalid_caller, Caller}) + ?RLX_ERROR({invalid_caller, Caller}) end. --spec check_lib_dirs([string()]) -> ok | relcool:error(). +-spec check_lib_dirs([string()]) -> ok | relx:error(). check_lib_dirs([]) -> ok; check_lib_dirs([Dir | Rest]) -> case filelib:is_dir(Dir) of false -> - ?RCL_ERROR({not_directory, Dir}); + ?RLX_ERROR({not_directory, Dir}); true -> check_lib_dirs(Rest) end. diff --git a/src/rlx_depsolver.erl b/src/rlx_depsolver.erl index 0cf8c88..2b6e420 100644 --- a/src/rlx_depsolver.erl +++ b/src/rlx_depsolver.erl @@ -41,8 +41,8 @@ %%% %%% we can add this world to the system all at once as follows %%% -%%% Graph0 = rcl_depsolver:new_graph(), -%%% Graph1 = rcl_depsolver:add_packages( +%%% Graph0 = rlx_depsolver:new_graph(), +%%% Graph1 = rlx_depsolver:add_packages( %%% [{app1, [{"0.1", [{app2, "0.2"}, %%% {app3, "0.2", '>='}]}, %%% {"0.2", []}, @@ -57,23 +57,23 @@ %%% We can also build it up incrementally using the other add_package and %%% add_package_version functions. %%% -%%% Finally, once we have built up the graph we can ask rcl_depsolver to solve the +%%% Finally, once we have built up the graph we can ask rlx_depsolver to solve the %%% dependency constraints. That is to give us a list of valid dependencies by %%% using the solve function. Lets say we want the app3 version "0.3" and all of %%% its resolved dependencies. We could call solve as follows. %%% -%%% rcl_depsolver:solve(Graph1, [{app3, "0.3"}]). +%%% rlx_depsolver:solve(Graph1, [{app3, "0.3"}]). %%% %%% That will give us the completely resolved dependencies including app3 %%% itself. Lets be a little more flexible. Lets ask for a graph that is rooted %%% in anything greater then or equal to app3 "0.3". We could do that by %%% -%%% rcl_depsolver:solve(Graph1, [{app3, "0.3", '>='}]). +%%% rlx_depsolver:solve(Graph1, [{app3, "0.3", '>='}]). %%% %%% Of course, you can specify any number of goals at the top level. %%% @end %%%------------------------------------------------------------------- --module(rcl_depsolver). +-module(rlx_depsolver). %% Public Api -export([format_error/1, @@ -91,7 +91,7 @@ is_valid_constraint/1, filter_packages/2]). -%% Internally Exported API. This should *not* be used outside of the rcl_depsolver +%% Internally Exported API. This should *not* be used outside of the rlx_depsolver %% application. You have been warned. -export([dep_pkg/1, filter_package/2, @@ -156,7 +156,7 @@ new_graph() -> %% @doc add a complete set of list of packages to the graph. Where the package %% consists of the name and a list of versions and dependencies. %% -%% ``` rcl_depsolver:add_packages(Graph, +%% ``` rlx_depsolver:add_packages(Graph, %% [{app1, [{"0.1", [{app2, "0.2"}, %% {app3, "0.2", '>='}]}, %% {"0.2", []}, @@ -177,7 +177,7 @@ add_packages(Dom0, Info) %% @doc add a single package to the graph, where it consists of a package name %% and its versions and thier dependencies. -%% ```rcl_depsolver:add_package(Graph, app1, [{"0.1", [{app2, "0.2"}, +%% ```rlx_depsolver:add_package(Graph, app1, [{"0.1", [{app2, "0.2"}, %% {app3, "0.2", '>='}]}, %% {"0.2", []}, %% {"0.3", []}]}]). @@ -193,7 +193,7 @@ add_package(State, Pkg, Versions) %% @doc add a set of dependencies to a specific package and version. %% and its versions and thier dependencies. -%% ```rcl_depsolver:add_package(Graph, app1, "0.1", [{app2, "0.2"}, +%% ```rlx_depsolver:add_package(Graph, app1, "0.1", [{app2, "0.2"}, %% {app3, "0.2", '>='}]}, %% {"0.2", []}, %% {"0.3", []}]). @@ -225,7 +225,7 @@ add_package_version({?MODULE, Dom0}, RawPkg, RawVsn, RawPkgConstraints) -> %% @doc add a package and version to the dependency graph with no dependency %% constraints, dependency constraints can always be added after the fact. %% -%% ```rcl_depsolver:add_package_version(Graph, app1, "0.1").''' +%% ```rlx_depsolver:add_package_version(Graph, app1, "0.1").''' -spec add_package_version(t(),pkg_name(),raw_vsn()) -> t(). add_package_version(State, Pkg, Vsn) -> add_package_version(State, Pkg, Vsn, []). @@ -233,7 +233,7 @@ add_package_version(State, Pkg, Vsn) -> %% @doc Given a set of goals (in the form of constrains) find a set of packages %% and versions that satisfy all constraints. If no solution can be found then %% an exception is thrown. -%% ``` rcl_depsolver:solve(State, [{app1, "0.1", '>='}]).''' +%% ``` rlx_depsolver:solve(State, [{app1, "0.1", '>='}]).''' -spec solve(t(),[constraint()]) -> {ok, [pkg()]} | {error, term()}. solve({?MODULE, DepGraph0}, RawGoals) when erlang:length(RawGoals) > 0 -> @@ -245,7 +245,7 @@ solve({?MODULE, DepGraph0}, RawGoals) case primitive_solve(DepGraph1, Goals, no_path) of {fail, _} -> [FirstCons | Rest] = Goals, - {error, rcl_depsolver_culprit:search(DepGraph1, [FirstCons], Rest)}; + {error, rlx_depsolver_culprit:search(DepGraph1, [FirstCons], Rest)}; Solution -> {ok, Solution} end @@ -317,7 +317,7 @@ filter_packages(PVPairs, RawConstraints) -> {invalid_constraints, [constraint()]} | list()}) -> iolist(). format_error(Error) -> - rcl_depsolver_culprit:format_error(Error). + rlx_depsolver_culprit:format_error(Error). %% @doc Return a formatted list of roots of the dependency trees which %% could not be satisified. These may also have versions attached. @@ -327,7 +327,7 @@ format_error(Error) -> %% -spec format_roots([constraints()]) -> iolist(). format_roots(Roots) -> - rcl_depsolver_culprit:format_roots(Roots). + rlx_depsolver_culprit:format_roots(Roots). %% @doc Return a formatted list of the culprit depenedencies which led to @@ -336,17 +336,17 @@ format_roots(Roots) -> %% ```(foo = 1.2.0) -> (bar > 2.0.0)``` -spec format_culprits([{[constraint()], [constraint()]}]) -> iolist(). format_culprits(Culprits) -> - rcl_depsolver_culprit:format_culprits(Culprits). + rlx_depsolver_culprit:format_culprits(Culprits). %% @doc A formatted version tuple -spec format_version(vsn()) -> iolist(). format_version(Version) -> - rcl_depsolver_culprit:format_version(Version). + rlx_depsolver_culprit:format_version(Version). %% @doc A formatted constraint tuple -spec format_constraint(constraint()) -> iolist(). format_constraint(Constraint) -> - rcl_depsolver_culprit:format_constraint(Constraint). + rlx_depsolver_culprit:format_constraint(Constraint). %%==================================================================== %% Internal Functions diff --git a/src/rlx_depsolver_culprit.erl b/src/rlx_depsolver_culprit.erl index f2115c7..cf6dcb2 100644 --- a/src/rlx_depsolver_culprit.erl +++ b/src/rlx_depsolver_culprit.erl @@ -28,7 +28,7 @@ %%% goal, and the good apps (those not immediately constrained from %%% the goals). %%% @end --module(rcl_depsolver_culprit). +-module(rlx_depsolver_culprit). -export([search/3, format_error/1, @@ -46,10 +46,10 @@ %%============================================================================ %% @doc start running the solver, with each run reduce the number of constraints %% set as goals. At some point the solver should succeed. --spec search(rcl_depsolver:dep_graph(), [rcl_depsolver:constraint()], [rcl_depsolver:constraint()]) +-spec search(rlx_depsolver:dep_graph(), [rlx_depsolver:constraint()], [rlx_depsolver:constraint()]) -> term(). search(State, ActiveCons, []) -> - case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of + case rlx_depsolver:primitive_solve(State, ActiveCons, keep_paths) of {fail, FailPaths} -> extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); _Success -> @@ -59,7 +59,7 @@ search(State, ActiveCons, []) -> inconsistant_graph_state end; search(State, ActiveCons, [NewCon | Constraints]) -> - case rcl_depsolver:primitive_solve(State, ActiveCons, keep_paths) of + case rlx_depsolver:primitive_solve(State, ActiveCons, keep_paths) of {fail, FailPaths} -> extract_culprit_information0(ActiveCons, lists:flatten(FailPaths)); _Success -> @@ -84,7 +84,7 @@ format_error(Details) when erlang:is_list(Details) -> ["Unable to solve constraints, the following solutions were attempted \n\n", [[format_error_path(" ", Detail)] || Detail <- Details]]. --spec format_roots([rcl_depsolver:constraints()]) -> iolist(). +-spec format_roots([rlx_depsolver:constraints()]) -> iolist(). format_roots(Roots) -> lists:foldl(fun(Root, Acc0) -> lists:foldl( @@ -95,9 +95,9 @@ format_roots(Roots) -> end, Acc0, Root) end, [], Roots). --spec format_culprits([{[rcl_depsolver:constraint()], [rcl_depsolver:constraint()]}]) -> iolist(). +-spec format_culprits([{[rlx_depsolver:constraint()], [rlx_depsolver:constraint()]}]) -> iolist(). format_culprits(FailingDeps) -> - Deps = sets:to_list(sets:from_list(lists:flatten([[rcl_depsolver:dep_pkg(Con) || Con <- Cons] + Deps = sets:to_list(sets:from_list(lists:flatten([[rlx_depsolver:dep_pkg(Con) || Con <- Cons] || {_, Cons} <- FailingDeps]))), lists:foldl(fun(Con, "") -> [format_constraint(Con)]; @@ -106,11 +106,11 @@ format_culprits(FailingDeps) -> ", " | Acc1] end, [], Deps). --spec format_version(rcl_depsolver:vsn()) -> iolist(). +-spec format_version(rlx_depsolver:vsn()) -> iolist(). format_version(Vsn) -> ec_semver:format(Vsn). --spec format_constraint(rcl_depsolver:constraint()) -> list(). +-spec format_constraint(rlx_depsolver:constraint()) -> list(). format_constraint(Pkg) when is_atom(Pkg) -> erlang:atom_to_list(Pkg); format_constraint(Pkg) when is_binary(Pkg) -> @@ -170,8 +170,8 @@ append_value(Key, Value, PropList) -> proplists:delete(Key, PropList)] end. --spec strip_goal([[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]) -> - [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()]. +-spec strip_goal([[rlx_depsolver:pkg()] | rlx_depsolver:pkg()]) -> + [[rlx_depsolver:pkg()] | rlx_depsolver:pkg()]. strip_goal([{'_GOAL_', 'NO_VSN'}, Children]) -> Children; strip_goal(All = [Val | _]) @@ -180,25 +180,25 @@ strip_goal(All = [Val | _]) strip_goal(Else) -> Else. --spec extract_culprit_information0(rcl_depsolver:constraints(), - [rcl_depsolver:fail_info()]) -> +-spec extract_culprit_information0(rlx_depsolver:constraints(), + [rlx_depsolver:fail_info()]) -> [term()]. extract_culprit_information0(ActiveCons, FailInfo) when is_list(FailInfo) -> [extract_culprit_information1(ActiveCons, FI) || FI <- FailInfo]. --spec extract_root(rcl_depsolver:constraints(), [rcl_depsolver:pkg()]) -> - {[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}. +-spec extract_root(rlx_depsolver:constraints(), [rlx_depsolver:pkg()]) -> + {[rlx_depsolver:constraint()], [rlx_depsolver:pkg()]}. extract_root(ActiveCons, TPath = [PRoot | _]) -> - RootName = rcl_depsolver:dep_pkg(PRoot), + RootName = rlx_depsolver:dep_pkg(PRoot), Roots = lists:filter(fun(El) -> - RootName =:= rcl_depsolver:dep_pkg(El) + RootName =:= rlx_depsolver:dep_pkg(El) end, ActiveCons), {Roots, TPath}. --spec extract_culprit_information1(rcl_depsolver:constraints(), - rcl_depsolver:fail_info()) -> +-spec extract_culprit_information1(rlx_depsolver:constraints(), + rlx_depsolver:fail_info()) -> term(). extract_culprit_information1(_ActiveCons, {[], RawConstraints}) -> %% In this case where there was no realized versions, the GOAL @@ -226,9 +226,9 @@ extract_culprit_information1(ActiveCons, {Path, RawConstraints}) -> RunListItems = [extract_root(ActiveCons, TPath) || TPath <- TreedPath], {RunListItems, FailCons}. --spec follow_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), - {rcl_depsolver:constraint(), rcl_depsolver:pkg()}) -> - false | {ok, rcl_depsolver:constraint()}. +-spec follow_chain(rlx_depsolver:pkg_name(), rlx_depsolver:vsn(), + {rlx_depsolver:constraint(), rlx_depsolver:pkg()}) -> + false | {ok, rlx_depsolver:constraint()}. follow_chain(Pkg, Vsn, {{Pkg, Vsn}, {Pkg, Vsn}}) -> %% When the package version is the same as the source we dont want to try to follow it at all false; @@ -237,9 +237,9 @@ follow_chain(Pkg, Vsn, {Con, {Pkg, Vsn}}) -> follow_chain(_Pkg, _Vsn, _) -> false. --spec find_chain(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), - [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> - rcl_depsolver:constraints(). +-spec find_chain(rlx_depsolver:pkg_name(), rlx_depsolver:vsn(), + [{rlx_depsolver:constraint(), rlx_depsolver:pkg()}]) -> + rlx_depsolver:constraints(). find_chain(Pkg, Vsn, Constraints) -> lists:foldl(fun(NCon, Acc) -> case follow_chain(Pkg, Vsn, NCon) of @@ -250,46 +250,46 @@ find_chain(Pkg, Vsn, Constraints) -> end end, [], Constraints). --spec get_constraints(rcl_depsolver:pkg_name(), rcl_depsolver:vsn(), [rcl_depsolver:pkg()], - [{rcl_depsolver:constraint(), rcl_depsolver:pkg()}]) -> - rcl_depsolver:constraints(). +-spec get_constraints(rlx_depsolver:pkg_name(), rlx_depsolver:vsn(), [rlx_depsolver:pkg()], + [{rlx_depsolver:constraint(), rlx_depsolver:pkg()}]) -> + rlx_depsolver:constraints(). get_constraints(FailedPkg, FailedVsn, Path, Constraints) -> Chain = find_chain(FailedPkg, FailedVsn, Constraints), lists:filter(fun(Con) -> - PkgName = rcl_depsolver:dep_pkg(Con), + PkgName = rlx_depsolver:dep_pkg(Con), (lists:any(fun(PathEl) -> - not rcl_depsolver:filter_package(PathEl, Con) + not rlx_depsolver:filter_package(PathEl, Con) end, Path) orelse not lists:keymember(PkgName, 1, Path)) end, Chain). --spec pkg_vsn(rcl_depsolver:constraint(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}]) -> - [rcl_depsolver:pkg()]. +-spec pkg_vsn(rlx_depsolver:constraint(), [{rlx_depsolver:constraint(), + rlx_depsolver:pkg()}]) -> + [rlx_depsolver:pkg()]. pkg_vsn(PkgCon, Constraints) -> - PkgName = rcl_depsolver:dep_pkg(PkgCon), + PkgName = rlx_depsolver:dep_pkg(PkgCon), [DepPkg || Con = {DepPkg, _} <- Constraints, case Con of {Pkg = {PkgName, PkgVsn}, {PkgName, PkgVsn}} -> - rcl_depsolver:filter_package(Pkg, PkgCon); + rlx_depsolver:filter_package(Pkg, PkgCon); _ -> false end]. --spec depends(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}], - [rcl_depsolver:pkg()]) -> - [rcl_depsolver:pkg()]. +-spec depends(rlx_depsolver:pkg(), [{rlx_depsolver:constraint(), + rlx_depsolver:pkg()}], + [rlx_depsolver:pkg()]) -> + [rlx_depsolver:pkg()]. depends(SrcPkg, Constraints, Seen) -> lists:flatten([pkg_vsn(Pkg, Constraints) || {Pkg, Source} <- Constraints, Source =:= SrcPkg andalso Pkg =/= SrcPkg andalso not lists:member(Pkg, Seen)]). --spec treeize_path(rcl_depsolver:pkg(), [{rcl_depsolver:constraint(), - rcl_depsolver:pkg()}], - [rcl_depsolver:pkg()]) -> - [rcl_depsolver:pkg() | [rcl_depsolver:pkg()]]. +-spec treeize_path(rlx_depsolver:pkg(), [{rlx_depsolver:constraint(), + rlx_depsolver:pkg()}], + [rlx_depsolver:pkg()]) -> + [rlx_depsolver:pkg() | [rlx_depsolver:pkg()]]. treeize_path(Pkg, Constraints, Seen0) -> Seen1 = [Pkg | Seen0], case depends(Pkg, Constraints, Seen1) of @@ -310,7 +310,7 @@ add_s(Roots) -> "" end. --spec format_path(string(), [rcl_depsolver:pkg()]) -> iolist(). +-spec format_path(string(), [rlx_depsolver:pkg()]) -> iolist(). format_path(CurrentIdent, Path) -> [CurrentIdent, " ", lists:foldl(fun(Con, "") -> @@ -320,8 +320,8 @@ format_path(CurrentIdent, Path) -> end, "", Path), "\n"]. --spec format_dependency_paths(string(), [[rcl_depsolver:pkg()] | rcl_depsolver:pkg()], - [{rcl_depsolver:pkg(), [rcl_depsolver:constraint()]}], [rcl_depsolver:pkg()]) -> iolist(). +-spec format_dependency_paths(string(), [[rlx_depsolver:pkg()] | rlx_depsolver:pkg()], + [{rlx_depsolver:pkg(), [rlx_depsolver:constraint()]}], [rlx_depsolver:pkg()]) -> iolist(). format_dependency_paths(CurrentIndent, [SubPath | Rest], FailingDeps, Acc) when erlang:is_list(SubPath) -> [format_dependency_paths(CurrentIndent, lists:sort(SubPath), FailingDeps, Acc), @@ -348,8 +348,8 @@ format_dependency_paths(CurrentIndent, [Con | Rest], FailingDeps, Acc) -> format_dependency_paths(_CurrentIndent, [], _FailingDeps, _Acc) -> []. --spec format_error_path(string(), {[{[rcl_depsolver:constraint()], [rcl_depsolver:pkg()]}], - [rcl_depsolver:constraint()]}) -> iolist(). +-spec format_error_path(string(), {[{[rlx_depsolver:constraint()], [rlx_depsolver:pkg()]}], + [rlx_depsolver:constraint()]}) -> iolist(). format_error_path(CurrentIndent, {RawPaths, FailingDeps}) -> Roots = [RootSet || {RootSet, _} <- RawPaths], Paths = [Path || {_, Path} <- RawPaths], diff --git a/src/rlx_dscv_util.erl b/src/rlx_dscv_util.erl index 68dcb68..3d90b08 100644 --- a/src/rlx_dscv_util.erl +++ b/src/rlx_dscv_util.erl @@ -21,13 +21,13 @@ %%% @doc This provider uses the lib_dir setting of the state. It searches the %%% Lib Dirs looking for all OTP Applications that are available. When it finds %%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_dscv_util). +%%% the state of available apps. This implements the rlx_provider behaviour. +-module(rlx_dscv_util). -export([do/2, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% Types diff --git a/src/rlx_goal.erl b/src/rlx_goal.erl index bd3f265..7226525 100644 --- a/src/rlx_goal.erl +++ b/src/rlx_goal.erl @@ -25,14 +25,14 @@ parse(Input) when is_binary(Input) -> [_,AppName,_,Op,_,Vsn,_, _] -> {ok, {AppName, - rcl_goal_utils:to_vsn(Vsn), - rcl_goal_utils:to_op(Op)}}; + rlx_goal_utils:to_vsn(Vsn), + rlx_goal_utils:to_op(Op)}}; [_,AppName,_,Op,_,Vsn1,_,_,_,Vsn2,_,_] -> {ok, {AppName, - rcl_goal_utils:to_vsn(Vsn1), - rcl_goal_utils:to_vsn(Vsn2), - rcl_goal_utils:to_op(Op)}}; + rlx_goal_utils:to_vsn(Vsn1), + rlx_goal_utils:to_vsn(Vsn2), + rlx_goal_utils:to_op(Op)}}; _ -> io:format("~p~n", [Node]) end diff --git a/src/rlx_goal.peg b/src/rlx_goal.peg index bfac607..08a3726 100644 --- a/src/rlx_goal.peg +++ b/src/rlx_goal.peg @@ -10,14 +10,14 @@ constraint <- ws? app_name ws? between_op ws? version ws? "," ws? version ws? [_,AppName,_,Op,_,Vsn,_, _] -> {ok, {AppName, - rcl_goal_utils:to_vsn(Vsn), - rcl_goal_utils:to_op(Op)}}; + rlx_goal_utils:to_vsn(Vsn), + rlx_goal_utils:to_op(Op)}}; [_,AppName,_,Op,_,Vsn1,_,_,_,Vsn2,_,_] -> {ok, {AppName, - rcl_goal_utils:to_vsn(Vsn1), - rcl_goal_utils:to_vsn(Vsn2), - rcl_goal_utils:to_op(Op)}}; + rlx_goal_utils:to_vsn(Vsn1), + rlx_goal_utils:to_vsn(Vsn2), + rlx_goal_utils:to_op(Op)}}; _ -> io:format("~p~n", [Node]) end diff --git a/src/rlx_goal_utils.erl b/src/rlx_goal_utils.erl index 6065e5c..764fab6 100644 --- a/src/rlx_goal_utils.erl +++ b/src/rlx_goal_utils.erl @@ -20,7 +20,7 @@ %%% %%% @doc %%% Utilities to help with parsing of target specs --module(rcl_goal_utils). +-module(rlx_goal_utils). -export([to_op/1, to_vsn/1]). @@ -32,7 +32,7 @@ %%============================================================================ %% API %%============================================================================ --spec to_op(iolist()) -> rcl_depsolver:constraint_op(). +-spec to_op(iolist()) -> rlx_depsolver:constraint_op(). to_op(List) when erlang:is_list(List) -> to_op(erlang:iolist_to_binary(List)); @@ -70,7 +70,7 @@ to_op(<<":between:">>) -> to_vsn(Version) when erlang:is_list(Version) -> to_vsn(erlang:iolist_to_binary(Version)); to_vsn(Vsn) -> - rcl_depsolver:parse_version(Vsn). + rlx_depsolver:parse_version(Vsn). %%%=================================================================== %%% Test Functions diff --git a/src/rlx_log.erl b/src/rlx_log.erl index 71c0b5d..901ae7c 100644 --- a/src/rlx_log.erl +++ b/src/rlx_log.erl @@ -18,9 +18,9 @@ %%% @author Eric Merritt %%% @copyright (C) 2012 Erlware, LLC. %%% -%%% @doc This provides simple output functions for relcool. You should use this +%%% @doc This provides simple output functions for relx. You should use this %%% to talk to the users if you are wrting code for the system --module(rcl_log). +-module(rlx_log). -export([new/1, log/4, @@ -41,7 +41,7 @@ log_fun/0, t/0]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% types @@ -83,7 +83,7 @@ new(AtomLogLevel) -spec debug(t(), string() | log_fun()) -> ok. debug(LogState, Fun) when erlang:is_function(Fun) -> - log(LogState, ?RCL_DEBUG, Fun); + log(LogState, ?RLX_DEBUG, Fun); debug(LogState, String) -> debug(LogState, "~s~n", [String]). @@ -91,14 +91,14 @@ debug(LogState, String) -> %% and argements @see io:format/2 -spec debug(t(), string(), [any()]) -> ok. debug(LogState, FormatString, Args) -> - log(LogState, ?RCL_DEBUG, FormatString, Args). + log(LogState, ?RLX_DEBUG, FormatString, Args). %% @doc log at the info level given the current log state with a string or %% function that returns a string -spec info(t(), string() | log_fun()) -> ok. info(LogState, Fun) when erlang:is_function(Fun) -> - log(LogState, ?RCL_INFO, Fun); + log(LogState, ?RLX_INFO, Fun); info(LogState, String) -> info(LogState, "~s~n", [String]). @@ -106,14 +106,14 @@ info(LogState, String) -> %% and argements @see io:format/2 -spec info(t(), string(), [any()]) -> ok. info(LogState, FormatString, Args) -> - log(LogState, ?RCL_INFO, FormatString, Args). + log(LogState, ?RLX_INFO, FormatString, Args). %% @doc log at the error level given the current log state with a string or %% format string that returns a function -spec error(t(), string() | log_fun()) -> ok. error(LogState, Fun) when erlang:is_function(Fun) -> - log(LogState, ?RCL_ERROR, Fun); + log(LogState, ?RLX_ERROR, Fun); error(LogState, String) -> error(LogState, "~s~n", [String]). @@ -121,7 +121,7 @@ error(LogState, String) -> %% and argements @see io:format/2 -spec error(t(), string(), [any()]) -> ok. error(LogState, FormatString, Args) -> - log(LogState, ?RCL_ERROR, FormatString, Args). + log(LogState, ?RLX_ERROR, FormatString, Args). %% @doc Execute the fun passed in if log level is as expected. -spec log(t(), int_log_level(), log_fun()) -> ok. @@ -158,11 +158,11 @@ log_level({?MODULE, DetailLogLevel}) -> %% @doc get the current log level as an atom -spec atom_log_level(t()) -> atom_log_level(). -atom_log_level({?MODULE, ?RCL_ERROR}) -> +atom_log_level({?MODULE, ?RLX_ERROR}) -> error; -atom_log_level({?MODULE, ?RCL_INFO}) -> +atom_log_level({?MODULE, ?RLX_INFO}) -> info; -atom_log_level({?MODULE, ?RCL_DEBUG}) -> +atom_log_level({?MODULE, ?RLX_DEBUG}) -> debug. -spec format(t()) -> iolist(). @@ -181,24 +181,24 @@ format(Log) -> should_test() -> ErrorLogState = new(error), - ?assertMatch(true, should(ErrorLogState, ?RCL_ERROR)), - ?assertMatch(true, not should(ErrorLogState, ?RCL_INFO)), - ?assertMatch(true, not should(ErrorLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_ERROR, log_level(ErrorLogState)), + ?assertMatch(true, should(ErrorLogState, ?RLX_ERROR)), + ?assertMatch(true, not should(ErrorLogState, ?RLX_INFO)), + ?assertMatch(true, not should(ErrorLogState, ?RLX_DEBUG)), + ?assertEqual(?RLX_ERROR, log_level(ErrorLogState)), ?assertEqual(error, atom_log_level(ErrorLogState)), InfoLogState = new(info), - ?assertMatch(true, should(InfoLogState, ?RCL_ERROR)), - ?assertMatch(true, should(InfoLogState, ?RCL_INFO)), - ?assertMatch(true, not should(InfoLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_INFO, log_level(InfoLogState)), + ?assertMatch(true, should(InfoLogState, ?RLX_ERROR)), + ?assertMatch(true, should(InfoLogState, ?RLX_INFO)), + ?assertMatch(true, not should(InfoLogState, ?RLX_DEBUG)), + ?assertEqual(?RLX_INFO, log_level(InfoLogState)), ?assertEqual(info, atom_log_level(InfoLogState)), DebugLogState = new(debug), - ?assertMatch(true, should(DebugLogState, ?RCL_ERROR)), - ?assertMatch(true, should(DebugLogState, ?RCL_INFO)), - ?assertMatch(true, should(DebugLogState, ?RCL_DEBUG)), - ?assertEqual(?RCL_DEBUG, log_level(DebugLogState)), + ?assertMatch(true, should(DebugLogState, ?RLX_ERROR)), + ?assertMatch(true, should(DebugLogState, ?RLX_INFO)), + ?assertMatch(true, should(DebugLogState, ?RLX_DEBUG)), + ?assertEqual(?RLX_DEBUG, log_level(DebugLogState)), ?assertEqual(debug, atom_log_level(DebugLogState)). -endif. diff --git a/src/rlx_provider.erl b/src/rlx_provider.erl index 4d8f044..b0de178 100644 --- a/src/rlx_provider.erl +++ b/src/rlx_provider.erl @@ -21,7 +21,7 @@ %%% A module that supports providing state manipulation services to the system. %%% @end %%%------------------------------------------------------------------- --module(rcl_provider). +-module(rlx_provider). %% API -export([new/2, @@ -33,7 +33,7 @@ -export_type([t/0]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%%=================================================================== %%% Types @@ -44,8 +44,8 @@ -ifdef(have_callback_support). --callback init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). --callback do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-callback init(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). +-callback do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). -callback format_error(Reason::term()) -> iolist(). -else. @@ -72,13 +72,13 @@ behaviour_info(_) -> %% %% @param ModuleName The module name. %% @param State0 The current state of the system --spec new(module(), rcl_state:t()) -> - {t(), {ok, rcl_state:t()}} | relcool:error(). +-spec new(module(), rlx_state:t()) -> + {t(), {ok, rlx_state:t()}} | relx:error(). new(ModuleName, State0) when is_atom(ModuleName) -> State1 = ModuleName:init(State0), case code:which(ModuleName) of non_existing -> - ?RCL_ERROR({non_existing, ModuleName}); + ?RLX_ERROR({non_existing, ModuleName}); _ -> {{?MODULE, ModuleName}, State1} end. @@ -87,8 +87,8 @@ new(ModuleName, State0) when is_atom(ModuleName) -> %% %% @param Provider the provider object %% @param State the current state of the system --spec do(Provider::t(), rcl_state:t()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec do(Provider::t(), rlx_state:t()) -> + {ok, rlx_state:t()} | relx:error(). do({?MODULE, Mod}, State) -> Mod:do(State). diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl index aca783d..18a372c 100644 --- a/src/rlx_prv_assembler.erl +++ b/src/rlx_prv_assembler.erl @@ -20,37 +20,37 @@ %%% %%% @doc Given a complete built release this provider assembles that release %%% into a release directory. --module(rcl_prv_assembler). +-module(rlx_prv_assembler). --behaviour(rcl_provider). +-behaviour(rlx_provider). -export([init/1, do/1, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API %%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. init(State) -> {ok, State}. %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). do(State) -> - {RelName, RelVsn} = rcl_state:default_configured_release(State), - Release = rcl_state:get_realized_release(State, RelName, RelVsn), - OutputDir = rcl_state:output_dir(State), + {RelName, RelVsn} = rlx_state:default_configured_release(State), + Release = rlx_state:get_realized_release(State, RelName, RelVsn), + OutputDir = rlx_state:output_dir(State), case create_output_dir(OutputDir) of ok -> - case rcl_release:realized(Release) of + case rlx_release:realized(Release) of true -> copy_app_directories_to_output(State, Release, OutputDir); false -> - ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + ?RLX_ERROR({unresolved_release, RelName, RelVsn}) end; Error -> Error @@ -73,19 +73,19 @@ format_error({release_script_generation_error, RelFile}) -> [RelFile]); format_error({release_script_generation_warning, Module, Warnings}) -> ["Warnings generating release \s", - rcl_util:indent(1), Module:format_warning(Warnings)]; + rlx_util:indent(1), Module:format_warning(Warnings)]; format_error({unable_to_create_output_dir, OutputDir}) -> io_lib:format("Unable to create output directory (possible permissions issue): ~s", [OutputDir]); format_error({release_script_generation_error, Module, Errors}) -> ["Errors generating release \n", - rcl_util:indent(1), Module:format_error(Errors)]; + rlx_util:indent(1), Module:format_error(Errors)]; format_error({relup_generation_error, CurrentName, UpFromName}) -> io_lib:format("Unknown internal release error generating the relup from ~s to ~s", [UpFromName, CurrentName]); format_error({relup_generation_warning, Module, Warnings}) -> ["Warnings generating relup \s", - rcl_util:indent(1), Module:format_warning(Warnings)]; + rlx_util:indent(1), Module:format_warning(Warnings)]; format_error({relup_script_generation_error, {relupcript_generation_error, systools_relup, {missing_sasl, _}}}) -> @@ -93,10 +93,10 @@ format_error({relup_script_generation_error, "in both the current release and the release to upgrade from."; format_error({relup_script_generation_error, Module, Errors}) -> ["Errors generating relup \n", - rcl_util:indent(1), Module:format_error(Errors)]; + rlx_util:indent(1), Module:format_error(Errors)]; format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", - [AppDir, TargetDir, rcl_util:indent(1), + [AppDir, TargetDir, rlx_util:indent(1), file:format_error(Reason)]). %%%=================================================================== @@ -107,11 +107,11 @@ format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> create_output_dir(OutputDir) -> case filelib:is_dir(OutputDir) of false -> - case rcl_util:mkdir_p(OutputDir) of + case rlx_util:mkdir_p(OutputDir) of ok -> ok; {error, _} -> - ?RCL_ERROR({unable_to_create_output_dir, OutputDir}) + ?RLX_ERROR({unable_to_create_output_dir, OutputDir}) end; true -> ok @@ -120,7 +120,7 @@ create_output_dir(OutputDir) -> copy_app_directories_to_output(State, Release, OutputDir) -> LibDir = filename:join([OutputDir, "lib"]), ok = ec_file:mkdir_p(LibDir), - Apps = rcl_release:application_details(Release), + Apps = rlx_release:application_details(Release), Result = lists:filter(fun({error, _}) -> true; (_) -> @@ -137,9 +137,9 @@ copy_app_directories_to_output(State, Release, OutputDir) -> end. copy_app(LibDir, App) -> - AppName = erlang:atom_to_list(rcl_app_info:name(App)), - AppVsn = rcl_app_info:vsn_as_string(App), - AppDir = rcl_app_info:dir(App), + AppName = erlang:atom_to_list(rlx_app_info:name(App)), + AppVsn = rlx_app_info:vsn_as_string(App), + AppDir = rlx_app_info:dir(App), TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), if AppDir == TargetDir -> @@ -152,7 +152,7 @@ copy_app(LibDir, App) -> copy_app(App, AppDir, TargetDir) -> remove_symlink_or_directory(TargetDir), - case rcl_app_info:link(App) of + case rlx_app_info:link(App) of true -> link_directory(AppDir, TargetDir); false -> @@ -175,7 +175,7 @@ remove_symlink_or_directory(TargetDir) -> link_directory(AppDir, TargetDir) -> case file:make_symlink(AppDir, TargetDir) of {error, Reason} -> - ?RCL_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); + ?RLX_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); ok -> ok end. @@ -196,10 +196,10 @@ copy_dir(AppDir, TargetDir, SubDir) -> SubTarget = filename:join(TargetDir, SubDir), case filelib:is_dir(SubSource) of true -> - ok = rcl_util:mkdir_p(SubTarget), + ok = rlx_util:mkdir_p(SubTarget), case ec_file:copy(SubSource, SubTarget, [recursive]) of {error, E} -> - ?RCL_ERROR({ec_file_error, AppDir, SubTarget, E}); + ?RLX_ERROR({ec_file_error, AppDir, SubTarget, E}); ok -> ok end; @@ -208,13 +208,13 @@ copy_dir(AppDir, TargetDir, SubDir) -> end. create_release_info(State0, Release0, OutputDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release0)), + RelName = erlang:atom_to_list(rlx_release:name(Release0)), ReleaseDir = release_output_dir(State0, Release0), ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), - Release1 = rcl_release:relfile(Release0, ReleaseFile), - State1 = rcl_state:update_realized_release(State0, Release1), - case rcl_release:metadata(Release1) of + Release1 = rlx_release:relfile(Release0, ReleaseFile), + State1 = rlx_state:update_realized_release(State0, Release1), + case rlx_release:metadata(Release1) of {ok, Meta} -> ok = ec_file:write_term(ReleaseFile, Meta), write_bin_file(State1, Release1, OutputDir, ReleaseDir); @@ -224,24 +224,24 @@ create_release_info(State0, Release0, OutputDir) -> write_bin_file(State, Release, OutputDir, RelDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release)), - RelVsn = rcl_release:vsn(Release), + RelName = erlang:atom_to_list(rlx_release:name(Release)), + RelVsn = rlx_release:vsn(Release), BinDir = filename:join([OutputDir, "bin"]), ok = ec_file:mkdir_p(BinDir), - VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), + VsnRel = filename:join(BinDir, rlx_release:canonical_name(Release)), BareRel = filename:join(BinDir, RelName), - ErlOpts = rcl_state:get(State, erl_opts, ""), - StartFile = case rcl_state:get(State, extended_start_script, false) of + ErlOpts = rlx_state:get(State, erl_opts, ""), + StartFile = case rlx_state:get(State, extended_start_script, false) of false -> bin_file_contents(RelName, RelVsn, - rcl_release:erts(Release), + rlx_release:erts(Release), ErlOpts); true -> - extended_bin_file_contents(RelName, RelVsn, rcl_release:erts(Release), ErlOpts) + extended_bin_file_contents(RelName, RelVsn, rlx_release:erts(Release), ErlOpts) end, %% We generate the start script by default, unless the user %% tells us not too - case rcl_state:get(State, generate_start_script, true) of + case rlx_state:get(State, generate_start_script, true) of false -> ok; _ -> @@ -254,19 +254,19 @@ write_bin_file(State, Release, OutputDir, RelDir) -> copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir). %% @doc copy config/sys.config or generate one to releases/VSN/sys.config --spec copy_or_generate_sys_config_file(rcl_state:t(), rcl_release:t(), +-spec copy_or_generate_sys_config_file(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). + {ok, rlx_state:t()} | relx:error(). copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir) -> RelSysConfPath = filename:join([RelDir, "sys.config"]), - case rcl_state:sys_config(State) of + case rlx_state:sys_config(State) of undefined -> ok = generate_sys_config_file(RelSysConfPath), include_erts(State, Release, OutputDir, RelDir); ConfigPath -> case filelib:is_regular(ConfigPath) of false -> - ?RCL_ERROR({config_does_not_exist, ConfigPath}); + ?RLX_ERROR({config_does_not_exist, ConfigPath}); true -> ok = ec_file:copy(ConfigPath, RelSysConfPath), include_erts(State, Release, OutputDir, RelDir) @@ -290,23 +290,23 @@ generate_sys_config_file(RelSysConfPath) -> file:close(Fd). %% @doc Optionally add erts directory to release, if defined. --spec include_erts(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> {ok, rcl_state:t()} | relcool:error(). +-spec include_erts(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> {ok, rlx_state:t()} | relx:error(). include_erts(State, Release, OutputDir, RelDir) -> - case rcl_state:get(State, include_erts, true) of + case rlx_state:get(State, include_erts, true) of true -> Prefix = code:root_dir(), - ErtsVersion = rcl_release:erts(Release), + ErtsVersion = rlx_release:erts(Release), ErtsDir = filename:join([Prefix, "erts-" ++ ErtsVersion]), LocalErts = filename:join([OutputDir, "erts-" ++ ErtsVersion]), case filelib:is_dir(ErtsDir) of false -> - ?RCL_ERROR({specified_erts_does_not_exist, ErtsVersion}); + ?RLX_ERROR({specified_erts_does_not_exist, ErtsVersion}); true -> ok = ec_file:mkdir_p(LocalErts), ok = ec_file:copy(ErtsDir, LocalErts, [recursive]), ok = ec_file:remove(filename:join([LocalErts, "bin", "erl"])), ok = file:write_file(filename:join([LocalErts, "bin", "erl"]), erl_script(ErtsVersion)), - case rcl_state:get(State, extended_start_script, false) of + case rlx_state:get(State, extended_start_script, false) of true -> ok = ec_file:copy(filename:join([Prefix, "bin", "start_clean.boot"]), filename:join([OutputDir, "bin", "start_clean.boot"])), @@ -324,32 +324,32 @@ include_erts(State, Release, OutputDir, RelDir) -> end. --spec make_boot_script(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec make_boot_script(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> + {ok, rlx_state:t()} | relx:error(). make_boot_script(State, Release, OutputDir, RelDir) -> Options = [{path, [RelDir | get_code_paths(Release, OutputDir)]}, {outdir, RelDir}, no_module_tests, silent], - Name = erlang:atom_to_list(rcl_release:name(Release)), + Name = erlang:atom_to_list(rlx_release:name(Release)), ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), case make_script(Options, fun(CorrectedOptions) -> systools:make_script(Name, CorrectedOptions) end) of ok -> - rcl_log:error(rcl_state:log(State), + rlx_log:error(rlx_state:log(State), "release successfully created!"), make_relup(State, Release); error -> - ?RCL_ERROR({release_script_generation_error, ReleaseFile}); + ?RLX_ERROR({release_script_generation_error, ReleaseFile}); {ok, _, []} -> - rcl_log:error(rcl_state:log(State), + rlx_log:error(rlx_state:log(State), "release successfully created!"), make_relup(State, Release); {ok,Module,Warnings} -> - ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); + ?RLX_ERROR({release_script_generation_warn, Module, Warnings}); {error,Module,Error} -> - ?RCL_ERROR({release_script_generation_error, Module, Error}) + ?RLX_ERROR({release_script_generation_error, Module, Error}) end. -spec make_script([term()], @@ -366,10 +366,10 @@ make_script(Options, RunFun) -> end. make_relup(State, Release) -> - case rcl_state:action(State) of + case rlx_state:action(State) of relup -> UpFrom = - case rcl_state:upfrom(State) of + case rlx_state:upfrom(State) of undefined -> get_last_release(State, Release); Vsn -> @@ -377,7 +377,7 @@ make_relup(State, Release) -> end, case UpFrom of undefined -> - ?RCL_ERROR(no_upfrom_release_found); + ?RLX_ERROR(no_upfrom_release_found); _ -> make_upfrom_script(State, Release, UpFrom) end; @@ -386,14 +386,14 @@ make_relup(State, Release) -> end. make_upfrom_script(State, Release, UpFrom) -> - OutputDir = rcl_state:output_dir(State), + OutputDir = rlx_state:output_dir(State), Options = [{outdir, OutputDir}, {path, get_code_paths(Release, OutputDir) ++ get_code_paths(UpFrom, OutputDir)}, silent], - CurrentRel = strip_rel(rcl_release:relfile(Release)), - UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), - rcl_log:debug(rcl_state:log(State), + CurrentRel = strip_rel(rlx_release:relfile(Release)), + UpFromRel = strip_rel(rlx_release:relfile(UpFrom)), + rlx_log:debug(rlx_state:log(State), "systools:make_relup(~p, ~p, ~p, ~p)", [CurrentRel, UpFromRel, UpFromRel, Options]), case make_script(Options, @@ -401,53 +401,53 @@ make_upfrom_script(State, Release, UpFrom) -> systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) end) of ok -> - rcl_log:error(rcl_state:log(State), + rlx_log:error(rlx_state:log(State), "relup from ~s to ~s successfully created!", [UpFromRel, CurrentRel]), {ok, State}; error -> - ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); + ?RLX_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); {ok, RelUp, _, []} -> - rcl_log:error(rcl_state:log(State), + rlx_log:error(rlx_state:log(State), "relup successfully created!"), write_relup_file(State, Release, RelUp), {ok, State}; {ok,_, Module,Warnings} -> - ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); + ?RLX_ERROR({relup_script_generation_warn, Module, Warnings}); {error,Module,Errors} -> - ?RCL_ERROR({relupcript_generation_error, Module, Errors}) + ?RLX_ERROR({relupcript_generation_error, Module, Errors}) end. write_relup_file(State, Release, Relup) -> OutDir = release_output_dir(State, Release), - RelName = rcl_util:to_string(rcl_release:name(Release)), + RelName = rlx_util:to_string(rlx_release:name(Release)), RelupFile = filename:join(OutDir, RelName ++ ".relup"), ok = ec_file:write_term(RelupFile, Relup). strip_rel(Name) -> - rcl_util:to_string(filename:join(filename:dirname(Name), + rlx_util:to_string(filename:join(filename:dirname(Name), filename:basename(Name, ".rel"))). get_up_release(State, Release, Vsn) -> - Name = rcl_release:name(Release), + Name = rlx_release:name(Release), try - ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) + ec_dictionary:get({Name, Vsn}, rlx_state:realized_releases(State)) catch throw:notfound -> undefined end. get_last_release(State, Release) -> - Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], + Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rlx_state:realized_releases(State))], Releases1 = lists:sort(fun(R1, R2) -> - ec_semver:lte(rcl_release:vsn(R1), - rcl_release:vsn(R2)) + ec_semver:lte(rlx_release:vsn(R1), + rlx_release:vsn(R2)) end, Releases0), Res = lists:foldl(fun(_Rel, R = {found, _}) -> R; (Rel, Prev) -> - case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of + case rlx_release:vsn(Rel) == rlx_release:vsn(Release) of true -> {found, Prev}; false -> @@ -461,21 +461,21 @@ get_last_release(State, Release) -> Else end. --spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). +-spec release_output_dir(rlx_state:t(), rlx_release:t()) -> string(). release_output_dir(State, Release) -> - OutputDir = rcl_state:output_dir(State), + OutputDir = rlx_state:output_dir(State), filename:join([OutputDir, "releases", - rcl_release:canonical_name(Release)]). + rlx_release:canonical_name(Release)]). %% @doc Generates the correct set of code paths for the system. --spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. +-spec get_code_paths(rlx_release:t(), file:name()) -> [file:name()]. get_code_paths(Release, OutDir) -> LibDir = filename:join(OutDir, "lib"), [filename:join([LibDir, - erlang:atom_to_list(rcl_app_info:name(App)) ++ "-" ++ - rcl_app_info:vsn_as_string(App), "ebin"]) || - App <- rcl_release:application_details(Release)]. + erlang:atom_to_list(rlx_app_info:name(App)) ++ "-" ++ + rlx_app_info:vsn_as_string(App), "ebin"]) || + App <- rlx_release:application_details(Release)]. erl_script(ErtsVsn) -> [<<"#!/bin/sh diff --git a/src/rlx_prv_config.erl b/src/rlx_prv_config.erl index 2bc9851..36a0527 100644 --- a/src/rlx_prv_config.erl +++ b/src/rlx_prv_config.erl @@ -21,31 +21,31 @@ %%% A module that provides config parsing and support to the system %%% @end %%%------------------------------------------------------------------- --module(rcl_prv_config). +-module(rlx_prv_config). --behaviour(rcl_provider). +-behaviour(rlx_provider). %% API -export([init/1, do/1, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%%=================================================================== %%% API %%%=================================================================== %% @doc Required by the system, but not used in this provider --spec init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-spec init(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). init(State) -> {ok, State}. %% @doc parse all the configs currently specified in the state, %% populating the state as a result. --spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error(). +-spec do(rlx_state:t()) ->{ok, rlx_state:t()} | relx:error(). do(State) -> - case rcl_state:config_file(State) of + case rlx_state:config_file(State) of [] -> search_for_dominating_config(State); undefined -> @@ -65,7 +65,7 @@ format_error({invalid_term, Term}) -> %%% Internal Functions %%%=================================================================== search_for_dominating_config({ok, Cwd}) -> - ConfigFile = filename:join(Cwd, "relcool.config"), + ConfigFile = filename:join(Cwd, "relx.config"), case ec_file:exists(ConfigFile) of true -> {ok, ConfigFile}; @@ -80,8 +80,8 @@ search_for_dominating_config(State0) -> {ok, Config} -> %% we need to set the root dir on state as well {ok, RootDir} = parent_dir(Config), - State1 = rcl_state:root_dir(State0, RootDir), - load_config(Config, rcl_state:config_file(State1, Config)); + State1 = rlx_state:root_dir(State0, RootDir), + load_config(Config, rlx_state:config_file(State1, Config)); no_config -> {ok, State0} end. @@ -106,24 +106,24 @@ parent_dir([H | T], Acc) -> parent_dir(T, [H | Acc]). --spec load_config(file:filename(), rcl_state:t()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec load_config(file:filename(), rlx_state:t()) -> + {ok, rlx_state:t()} | relx:error(). load_config(ConfigFile, State) -> {ok, CurrentCwd} = file:get_cwd(), ok = file:set_cwd(filename:dirname(ConfigFile)), Result = case file:consult(ConfigFile) of {error, Reason} -> - ?RCL_ERROR({consult, ConfigFile, Reason}); + ?RLX_ERROR({consult, ConfigFile, Reason}); {ok, Terms} -> lists:foldl(fun load_terms/2, {ok, State}, Terms) end, ok = file:set_cwd(CurrentCwd), Result. --spec load_terms(term(), {ok, rcl_state:t()} | relcool:error()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec load_terms(term(), {ok, rlx_state:t()} | relx:error()) -> + {ok, rlx_state:t()} | relx:error(). load_terms({default_release, RelName, RelVsn}, {ok, State}) -> - {ok, rcl_state:default_configured_release(State, RelName, RelVsn)}; + {ok, rlx_state:default_configured_release(State, RelName, RelVsn)}; load_terms({paths, Paths}, {ok, State}) -> code:add_pathsa([filename:absname(Path) || Path <- Paths]), {ok, State}; @@ -133,7 +133,7 @@ load_terms({providers, Providers0}, {ok, State0}) -> {_, E={error, _}} -> E; {Providers3, {ok, State3}} -> - {ok, rcl_state:providers(State3, Providers3)} + {ok, rlx_state:providers(State3, Providers3)} end; load_terms({add_providers, Providers0}, {ok, State0}) -> Providers1 = gen_providers(Providers0, State0), @@ -141,45 +141,45 @@ load_terms({add_providers, Providers0}, {ok, State0}) -> {_, E={error, _}} -> E; {Providers3, {ok, State1}} -> - ExistingProviders = rcl_state:providers(State1), - {ok, rcl_state:providers(State1, ExistingProviders ++ Providers3)} + ExistingProviders = rlx_state:providers(State1), + {ok, rlx_state:providers(State1, ExistingProviders ++ Providers3)} end; load_terms({skip_apps, SkipApps0}, {ok, State0}) -> - {ok, rcl_state:skip_apps(State0, SkipApps0)}; + {ok, rlx_state:skip_apps(State0, SkipApps0)}; load_terms({overrides, Overrides0}, {ok, State0}) -> - {ok, rcl_state:overrides(State0, Overrides0)}; + {ok, rlx_state:overrides(State0, Overrides0)}; load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) -> - Release0 = rcl_release:new(RelName, Vsn), - case rcl_release:goals(Release0, Applications) of + Release0 = rlx_release:new(RelName, Vsn), + case rlx_release:goals(Release0, Applications) of E={error, _} -> E; {ok, Release1} -> - {ok, rcl_state:add_configured_release(State0, Release1)} + {ok, rlx_state:add_configured_release(State0, Release1)} end; load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications}, {ok, State}) -> - Release0 = rcl_release:erts(rcl_release:new(RelName, Vsn), ErtsVsn), - case rcl_release:goals(Release0, Applications) of + Release0 = rlx_release:erts(rlx_release:new(RelName, Vsn), ErtsVsn), + case rlx_release:goals(Release0, Applications) of E={error, _} -> E; {ok, Release1} -> - {ok, rcl_state:add_configured_release(State, Release1)} + {ok, rlx_state:add_configured_release(State, Release1)} end; load_terms({sys_config, SysConfig}, {ok, State}) -> - {ok, rcl_state:sys_config(State, filename:absname(SysConfig))}; + {ok, rlx_state:sys_config(State, filename:absname(SysConfig))}; load_terms({Name, Value}, {ok, State}) when erlang:is_atom(Name) -> - {ok, rcl_state:put(State, Name, Value)}; + {ok, rlx_state:put(State, Name, Value)}; load_terms(_, Error={error, _}) -> Error; load_terms(InvalidTerm, _) -> - ?RCL_ERROR({invalid_term, InvalidTerm}). + ?RLX_ERROR({invalid_term, InvalidTerm}). --spec gen_providers([module()], rcl_state:t()) -> - {[rcl_provider:t()], {ok, rcl_state:t()} | relcool:error()}. +-spec gen_providers([module()], rlx_state:t()) -> + {[rlx_provider:t()], {ok, rlx_state:t()} | relx:error()}. gen_providers(Providers, State) -> lists:foldl(fun(ProviderName, {Providers1, {ok, State1}}) -> - {Provider, State2} = rcl_provider:new(ProviderName, State1), + {Provider, State2} = rlx_provider:new(ProviderName, State1), {[Provider | Providers1], State2}; (_, E={_, {error, _}}) -> E diff --git a/src/rlx_prv_discover.erl b/src/rlx_prv_discover.erl index 56085ac..5e54828 100644 --- a/src/rlx_prv_discover.erl +++ b/src/rlx_prv_discover.erl @@ -21,34 +21,34 @@ %%% @doc This provider uses the lib_dir setting of the state. It searches the %%% Lib Dirs looking for all OTP Applications that are available. When it finds %%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_prv_discover). --behaviour(rcl_provider). +%%% the state of available apps. This implements the rlx_provider behaviour. +-module(rlx_prv_discover). +-behaviour(rlx_provider). -export([init/1, do/1, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API %%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. init(State) -> {ok, State}. %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). do(State0) -> LibDirs = get_lib_dirs(State0), - case rcl_app_discovery:do(State0, LibDirs) of + case rlx_app_discovery:do(State0, LibDirs) of {ok, AppMeta} -> - case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of + case rlx_rel_discovery:do(State0, LibDirs, AppMeta) of {ok, Releases} -> - State1 = rcl_state:available_apps(State0, AppMeta), - {ok, rcl_state:realized_releases(State1, lists:foldl(fun add/2, + State1 = rlx_state:available_apps(State0, AppMeta), + {ok, rlx_state:realized_releases(State1, lists:foldl(fun add/2, ec_dictionary:new(ec_dict), Releases))}; Error -> @@ -69,13 +69,13 @@ format_error(_) -> %%%=================================================================== %% @doc only add the release if its not documented in the system add(Rel, Dict) -> - RelName = rcl_release:name(Rel), - RelVsn = rcl_release:vsn(Rel), + RelName = rlx_release:name(Rel), + RelVsn = rlx_release:vsn(Rel), ec_dictionary:add({RelName, RelVsn}, Rel, Dict). get_lib_dirs(State) -> - LibDirs0 = rcl_state:lib_dirs(State), - case rcl_state:get(State, disable_default_libs, false) of + LibDirs0 = rlx_state:lib_dirs(State), + case rlx_state:get(State, disable_default_libs, false) of true -> LibDirs0; false -> @@ -85,15 +85,15 @@ get_lib_dirs(State) -> add_release_output_dir(State)]) end. --spec add_common_project_dirs(rcl_state:t()) -> [file:name()]. +-spec add_common_project_dirs(rlx_state:t()) -> [file:name()]. add_common_project_dirs(State) -> %% Check to see if there is a rebar.config. If so then look for a deps %% dir. If both are there then we add that to the lib dirs. - case rcl_state:get(State, disable_project_subdirs, false) of + case rlx_state:get(State, disable_project_subdirs, false) of true -> []; false -> - Root = rcl_state:root_dir(State), + Root = rlx_state:root_dir(State), Apps = filename:join(Root, "apps"), Lib = filename:join(Root, "lib"), Deps = filename:join(Root, "deps"), @@ -108,9 +108,9 @@ add_common_project_dirs(State) -> end, [], [Deps, Lib, Apps, Ebin]) end. --spec add_system_lib_dir(rcl_state:t()) -> [file:name()]. +-spec add_system_lib_dir(rlx_state:t()) -> [file:name()]. add_system_lib_dir(State) -> - ExcludeSystem = rcl_state:get(State, discover_exclude_system, false), + ExcludeSystem = rlx_state:get(State, discover_exclude_system, false), case ExcludeSystem of true -> []; @@ -119,11 +119,11 @@ add_system_lib_dir(State) -> end. add_release_output_dir(State) -> - case rcl_state:get(State, disable_discover_release_output, false) of + case rlx_state:get(State, disable_discover_release_output, false) of true -> []; false -> - Output = erlang:iolist_to_binary(rcl_state:output_dir(State)), + Output = erlang:iolist_to_binary(rlx_state:output_dir(State)), case ec_file:exists(erlang:binary_to_list(Output)) of true -> Output; diff --git a/src/rlx_prv_overlay.erl b/src/rlx_prv_overlay.erl index 35e37bf..5fffd78 100644 --- a/src/rlx_prv_overlay.erl +++ b/src/rlx_prv_overlay.erl @@ -20,9 +20,9 @@ %%% %%% @doc Given a complete built release this provider assembles that release %%% into a release directory. --module(rcl_prv_overlay). +-module(rlx_prv_overlay). --behaviour(rcl_provider). +-behaviour(rlx_provider). -export([init/1, do/1, @@ -30,26 +30,26 @@ -define(DIRECTORY_RE, ".*(\/|\\\\)$"). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API %%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. init(State) -> {ok, State}. %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). do(State) -> - {RelName, RelVsn} = rcl_state:default_configured_release(State), - Release = rcl_state:get_realized_release(State, RelName, RelVsn), - case rcl_release:realized(Release) of + {RelName, RelVsn} = rlx_state:default_configured_release(State), + Release = rlx_state:get_realized_release(State, RelName, RelVsn), + case rlx_release:realized(Release) of true -> generate_overlay_vars(State, Release); false -> - ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + ?RLX_ERROR({unresolved_release, RelName, RelVsn}) end. -spec format_error(ErrorDetail::term()) -> iolist(). @@ -62,13 +62,13 @@ format_error({unable_to_read_varsfile, FileName, Reason}) -> io_lib:format("Unable to read vars file (~s) for overlay due to: ~p", [FileName, Reason]); format_error({overlay_failed, Errors}) -> - [[format_error(rcl_util:error_reason(Error)), "\n"] || Error <- Errors]; + [[format_error(rlx_util:error_reason(Error)), "\n"] || Error <- Errors]; format_error({dir_render_failed, Dir, Error}) -> io_lib:format("rendering mkdir path failed ~s with ~p", [Dir, Error]); format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", - [AppDir, TargetDir, rcl_util:indent(1), + [AppDir, TargetDir, rlx_util:indent(1), file:format_error(Reason)]); format_error({copy_failed, FromFile, ToFile, Err}) -> io_lib:format("Unable to copy from ~s to ~s because of ~p", @@ -92,25 +92,25 @@ format_error({unable_to_make_dir, Absolute, Error}) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== --spec generate_overlay_vars(rcl_state:t(), rcl_release:t()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec generate_overlay_vars(rlx_state:t(), rlx_release:t()) -> + {ok, rlx_state:t()} | relx:error(). generate_overlay_vars(State, Release) -> StateVars = generate_state_vars(State), ReleaseVars = generate_release_vars(Release), get_overlay_vars_from_file(State, StateVars ++ ReleaseVars). --spec get_overlay_vars_from_file(rcl_state:t(), proplists:proplist()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec get_overlay_vars_from_file(rlx_state:t(), proplists:proplist()) -> + {ok, rlx_state:t()} | relx:error(). get_overlay_vars_from_file(State, OverlayVars) -> - case rcl_state:get(State, overlay_vars, undefined) of + case rlx_state:get(State, overlay_vars, undefined) of undefined -> do_overlay(State, OverlayVars); FileName -> read_overlay_vars(State, OverlayVars, FileName) end. --spec read_overlay_vars(rcl_state:t(), proplists:proplist(), file:name()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec read_overlay_vars(rlx_state:t(), proplists:proplist(), file:name()) -> + {ok, rlx_state:t()} | relx:error(). read_overlay_vars(State, OverlayVars, FileName) -> RelativeRoot = get_relative_root(State), RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)), @@ -123,12 +123,12 @@ read_overlay_vars(State, OverlayVars, FileName) -> Error end; {error, Reason} -> - ?RCL_ERROR({unable_to_read_varsfile, FileName, Reason}) + ?RLX_ERROR({unable_to_read_varsfile, FileName, Reason}) end. -spec render_overlay_vars(proplists:proplist(), proplists:proplist(), proplists:proplist()) -> - {ok, proplists:proplist()} | relcool:error(). + {ok, proplists:proplist()} | relx:error(). render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) when erlang:is_list(Value) -> case io_lib:printable_list(Value) of @@ -161,66 +161,66 @@ render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) -> render_overlay_vars(_OverlayVars, [], Acc) -> {ok, Acc}. --spec generate_release_vars(rcl_release:t()) -> proplists:proplist(). +-spec generate_release_vars(rlx_release:t()) -> proplists:proplist(). generate_release_vars(Release) -> - [{erts_vsn, rcl_release:erts(Release)}, - {release_erts_version, rcl_release:erts(Release)}, - {release_name, rcl_release:name(Release)}, - {rel_vsn, rcl_release:vsn(Release)}, - {release_version, rcl_release:vsn(Release)}, + [{erts_vsn, rlx_release:erts(Release)}, + {release_erts_version, rlx_release:erts(Release)}, + {release_name, rlx_release:name(Release)}, + {rel_vsn, rlx_release:vsn(Release)}, + {release_version, rlx_release:vsn(Release)}, {release_applications, lists:map(fun(App) -> - rcl_app_info:name(App) - end, rcl_release:application_details(Release))}, - {release, [generate_app_vars(App)|| App <- rcl_release:application_details(Release)]}, + rlx_app_info:name(App) + end, rlx_release:application_details(Release))}, + {release, [generate_app_vars(App)|| App <- rlx_release:application_details(Release)]}, {release_goals, [if erlang:is_list(Constraint) -> Constraint; true -> - rcl_depsolver:format_constraint(Constraint) - end || Constraint <- rcl_release:goals(Release)]}]. + rlx_depsolver:format_constraint(Constraint) + end || Constraint <- rlx_release:goals(Release)]}]. --spec generate_app_vars(rcl_app_info:t()) -> AppInfo::tuple(). +-spec generate_app_vars(rlx_app_info:t()) -> AppInfo::tuple(). generate_app_vars(App) -> - {rcl_app_info:name(App), - [{version, rcl_app_info:vsn_as_string(App)}, - {dir, rcl_app_info:dir(App)}, - {active_dependencies, rcl_app_info:active_deps(App)}, - {library_dependencies, rcl_app_info:library_deps(App)}, - {link, rcl_app_info:link(App)}]}. + {rlx_app_info:name(App), + [{version, rlx_app_info:vsn_as_string(App)}, + {dir, rlx_app_info:dir(App)}, + {active_dependencies, rlx_app_info:active_deps(App)}, + {library_dependencies, rlx_app_info:library_deps(App)}, + {link, rlx_app_info:link(App)}]}. --spec generate_state_vars(rcl_state:t()) -> proplists:proplist(). +-spec generate_state_vars(rlx_state:t()) -> proplists:proplist(). generate_state_vars(State) -> - [{log, rcl_log:format(rcl_state:log(State))}, - {output_dir, rcl_state:output_dir(State)}, - {target_dir, rcl_state:output_dir(State)}, - {overridden, [AppName || {AppName, _} <- rcl_state:overrides(State)]}, - {overrides, rcl_state:overrides(State)}, - {goals, [rcl_depsolver:format_constraint(Constraint) || - Constraint <- rcl_state:goals(State)]}, - {lib_dirs, rcl_state:lib_dirs(State)}, - {config_file, rcl_state:config_file(State)}, - {providers, rcl_state:providers(State)}, - {sys_config, rcl_state:sys_config(State)}, - {root_dir, rcl_state:root_dir(State)}, - {default_release_name, case rcl_state:default_configured_release(State) of + [{log, rlx_log:format(rlx_state:log(State))}, + {output_dir, rlx_state:output_dir(State)}, + {target_dir, rlx_state:output_dir(State)}, + {overridden, [AppName || {AppName, _} <- rlx_state:overrides(State)]}, + {overrides, rlx_state:overrides(State)}, + {goals, [rlx_depsolver:format_constraint(Constraint) || + Constraint <- rlx_state:goals(State)]}, + {lib_dirs, rlx_state:lib_dirs(State)}, + {config_file, rlx_state:config_file(State)}, + {providers, rlx_state:providers(State)}, + {sys_config, rlx_state:sys_config(State)}, + {root_dir, rlx_state:root_dir(State)}, + {default_release_name, case rlx_state:default_configured_release(State) of {Name0, _} -> Name0 end}, - {default_release_version, case rcl_state:default_configured_release(State) of + {default_release_version, case rlx_state:default_configured_release(State) of {_, Vsn0} -> Vsn0 end}, - {default_release, case rcl_state:default_configured_release(State) of + {default_release, case rlx_state:default_configured_release(State) of {Name1, undefined} -> erlang:atom_to_list(Name1); {Name1, Vsn1} -> erlang:atom_to_list(Name1) ++ "-" ++ Vsn1 end}]. --spec do_overlay(rcl_state:t(), proplists:proplist()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec do_overlay(rlx_state:t(), proplists:proplist()) -> + {ok, rlx_state:t()} | relx:error(). do_overlay(State, OverlayVars) -> - case rcl_state:get(State, overlay, undefined) of + case rlx_state:get(State, overlay, undefined) of undefined -> {ok, State}; Overlays -> @@ -231,44 +231,44 @@ do_overlay(State, OverlayVars) -> end, Overlays)) end. --spec handle_errors(rcl_state:t(), [ok | relcool:error()]) -> - {ok, rcl_state:t()} | relcool:error(). +-spec handle_errors(rlx_state:t(), [ok | relx:error()]) -> + {ok, rlx_state:t()} | relx:error(). handle_errors(State, Result) -> case [Error || Error <- Result, - rcl_util:is_error(Error)] of + rlx_util:is_error(Error)] of Errors = [_|_] -> - ?RCL_ERROR({overlay_failed, Errors}); + ?RLX_ERROR({overlay_failed, Errors}); [] -> {ok, State} end. --spec do_individual_overlay(rcl_state:t(), proplists:proplist(), +-spec do_individual_overlay(rlx_state:t(), proplists:proplist(), OverlayDirective::term()) -> - {ok, rcl_state:t()} | relcool:error(). + {ok, rlx_state:t()} | relx:error(). do_individual_overlay(State, OverlayVars, {mkdir, Dir}) -> - ModuleName = make_template_name("rcl_mkdir_template", Dir), + ModuleName = make_template_name("rlx_mkdir_template", Dir), case erlydtl:compile(erlang:iolist_to_binary(Dir), ModuleName) of {ok, ModuleName} -> case render(ModuleName, OverlayVars) of {ok, IoList} -> Absolute = absolutize(State, - filename:join(rcl_state:output_dir(State), + filename:join(rlx_state:output_dir(State), erlang:iolist_to_binary(IoList))), - case rcl_util:mkdir_p(Absolute) of + case rlx_util:mkdir_p(Absolute) of {error, Error} -> - ?RCL_ERROR({unable_to_make_dir, Absolute, Error}); + ?RLX_ERROR({unable_to_make_dir, Absolute, Error}); ok -> ok end; {error, Error} -> - ?RCL_ERROR({dir_render_failed, Dir, Error}) + ?RLX_ERROR({dir_render_failed, Dir, Error}) end; {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Dir, Reason}) + ?RLX_ERROR({unable_to_compile_template, Dir, Reason}) end; do_individual_overlay(State, OverlayVars, {copy, From, To}) -> - FromTemplateName = make_template_name("rcl_copy_from_template", From), - ToTemplateName = make_template_name("rcl_copy_to_template", To), + FromTemplateName = make_template_name("rlx_copy_from_template", From), + ToTemplateName = make_template_name("rlx_copy_to_template", To), file_render_do(OverlayVars, From, FromTemplateName, fun(FromFile) -> file_render_do(OverlayVars, To, ToTemplateName, @@ -277,8 +277,8 @@ do_individual_overlay(State, OverlayVars, {copy, From, To}) -> end) end); do_individual_overlay(State, OverlayVars, {template, From, To}) -> - FromTemplateName = make_template_name("rcl_template_from_template", From), - ToTemplateName = make_template_name("rcl_template_to_template", To), + FromTemplateName = make_template_name("rlx_template_from_template", From), + ToTemplateName = make_template_name("rlx_template_to_template", To), file_render_do(OverlayVars, From, FromTemplateName, fun(FromFile) -> file_render_do(OverlayVars, To, ToTemplateName, @@ -291,15 +291,15 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> write_template(OverlayVars, FromFile1, absolutize(State, - filename:join(rcl_state:output_dir(State), + filename:join(rlx_state:output_dir(State), erlang:iolist_to_binary(ToFile)))) end) end). --spec copy_to(rcl_state:t(), file:name(), file:name()) -> ok | relcool:error(). +-spec copy_to(rlx_state:t(), file:name(), file:name()) -> ok | relx:error(). copy_to(State, FromFile0, ToFile0) -> RelativeRoot = get_relative_root(State), - ToFile1 = absolutize(State, filename:join(rcl_state:output_dir(State), + ToFile1 = absolutize(State, filename:join(rlx_state:output_dir(State), erlang:iolist_to_binary(ToFile0))), FromFile1 = absolutize(State, filename:join(RelativeRoot, @@ -309,7 +309,7 @@ copy_to(State, FromFile0, ToFile0) -> filelib:ensure_dir(ToFile1), ToFile1; true -> - rcl_util:mkdir_p(ToFile1), + rlx_util:mkdir_p(ToFile1), erlang:iolist_to_binary(filename:join(ToFile1, filename:basename(FromFile1))) end, @@ -319,15 +319,15 @@ copy_to(State, FromFile0, ToFile0) -> ok = file:write_file_info(ToFile2, FileInfo), ok; {error, Err} -> - ?RCL_ERROR({copy_failed, + ?RLX_ERROR({copy_failed, FromFile1, ToFile1, Err}) end. get_relative_root(State) -> - case rcl_state:config_file(State) of + case rlx_state:config_file(State) of [] -> - rcl_state:root_dir(State); + rlx_state:root_dir(State); Config -> filename:dirname(Config) end. @@ -343,19 +343,19 @@ is_directory(ToFile0, ToFile1) -> -spec render_template(proplists:proplist(), iolist()) -> - ok | relcool:error(). + ok | relx:error(). render_template(OverlayVars, Data) -> - TemplateName = make_template_name("rcl_template_renderer", Data), + TemplateName = make_template_name("rlx_template_renderer", Data), case erlydtl:compile(Data, TemplateName) of Good when Good =:= ok; Good =:= {ok, TemplateName} -> case render(TemplateName, OverlayVars) of {ok, IoData} -> {ok, IoData}; {error, Reason} -> - ?RCL_ERROR({unable_to_render_template, Data, Reason}) + ?RLX_ERROR({unable_to_render_template, Data, Reason}) end; {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Data, Reason}) + ?RLX_ERROR({unable_to_compile_template, Data, Reason}) end. write_template(OverlayVars, FromFile, ToFile) -> @@ -369,18 +369,18 @@ write_template(OverlayVars, FromFile, ToFile) -> ok = file:write_file_info(ToFile, FileInfo), ok; {error, Reason} -> - ?RCL_ERROR({unable_to_write, ToFile, Reason}) + ?RLX_ERROR({unable_to_write, ToFile, Reason}) end; {error, Reason} -> - ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason}) + ?RLX_ERROR({unable_to_enclosing_dir, ToFile, Reason}) end; Error -> Error end. -spec file_render_do(proplists:proplist(), iolist(), module(), - fun((term()) -> {ok, rcl_state:t()} | relcool:error())) -> - {ok, rcl_state:t()} | relcool:error(). + fun((term()) -> {ok, rlx_state:t()} | relx:error())) -> + {ok, rlx_state:t()} | relx:error(). file_render_do(OverlayVars, Data, TemplateName, NextAction) -> case erlydtl:compile(erlang:iolist_to_binary(Data), TemplateName) of {ok, TemplateName} -> @@ -388,10 +388,10 @@ file_render_do(OverlayVars, Data, TemplateName, NextAction) -> {ok, IoList} -> NextAction(IoList); {error, Error} -> - ?RCL_ERROR({render_failed, Data, Error}) + ?RLX_ERROR({render_failed, Data, Error}) end; {error, Reason} -> - ?RCL_ERROR({unable_to_compile_template, Data, Reason}) + ?RLX_ERROR({unable_to_compile_template, Data, Reason}) end. -spec make_template_name(string(), term()) -> module(). @@ -414,5 +414,5 @@ render(ModuleName, OverlayVars) -> end. absolutize(State, FileName) -> - filename:absname(filename:join(rcl_state:root_dir(State), + filename:absname(filename:join(rlx_state:root_dir(State), erlang:iolist_to_binary(FileName))). diff --git a/src/rlx_prv_release.erl b/src/rlx_prv_release.erl index d90b492..10e1eb0 100644 --- a/src/rlx_prv_release.erl +++ b/src/rlx_prv_release.erl @@ -21,27 +21,27 @@ %%% @doc This provider uses the lib_dir setting of the state. It searches the %%% Lib Dirs looking for all OTP Applications that are available. When it finds %%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_prv_release). +%%% the state of available apps. This implements the rlx_provider behaviour. +-module(rlx_prv_release). --behaviour(rcl_provider). +-behaviour(rlx_provider). -export([init/1, do/1, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API %%============================================================================ --spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. init(State) -> {ok, State}. %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). do(State) -> DepGraph = create_dep_graph(State), find_default_release(State, DepGraph). @@ -65,37 +65,37 @@ format_error({release_not_found, {RelName, RelVsn}}) -> io_lib:format("No releases exist in the system for ~p:~s!", [RelName, RelVsn]); format_error({failed_solve, Error}) -> io_lib:format("Failed to solve release:\n ~s", - [rcl_depsolver:format_error({error, Error})]). + [rlx_depsolver:format_error({error, Error})]). %%%=================================================================== %%% Internal Functions %%%=================================================================== --spec create_dep_graph(rcl_state:t()) -> rcl_depsolver:t(). +-spec create_dep_graph(rlx_state:t()) -> rlx_depsolver:t(). create_dep_graph(State) -> - Apps = rcl_state:available_apps(State), - Graph0 = rcl_depsolver:new_graph(), + Apps = rlx_state:available_apps(State), + Graph0 = rlx_depsolver:new_graph(), lists:foldl(fun(App, Graph1) -> - AppName = rcl_app_info:name(App), - AppVsn = rcl_app_info:vsn(App), - Deps = rcl_app_info:active_deps(App) ++ - rcl_app_info:library_deps(App), - rcl_depsolver:add_package_version(Graph1, + AppName = rlx_app_info:name(App), + AppVsn = rlx_app_info:vsn(App), + Deps = rlx_app_info:active_deps(App) ++ + rlx_app_info:library_deps(App), + rlx_depsolver:add_package_version(Graph1, AppName, AppVsn, Deps) end, Graph0, Apps). --spec find_default_release(rcl_state:t(), rcl_depsolver:t()) -> - {ok, rcl_state:t()} | relcool:error(). +-spec find_default_release(rlx_state:t(), rlx_depsolver:t()) -> + {ok, rlx_state:t()} | relx:error(). find_default_release(State, DepGraph) -> - case rcl_state:default_configured_release(State) of + case rlx_state:default_configured_release(State) of {undefined, undefined} -> resolve_default_release(State, DepGraph); {RelName, undefined} -> resolve_default_version(State, DepGraph, RelName); {undefined, Vsn} -> - ?RCL_ERROR({no_release_name, Vsn}); + ?RLX_ERROR({no_release_name, Vsn}); {RelName, RelVsn} -> solve_release(State, DepGraph, RelName, RelVsn) end. @@ -103,29 +103,29 @@ find_default_release(State, DepGraph) -> resolve_default_release(State0, DepGraph) -> %% Here we will just get the highest versioned release and run that. case lists:sort(fun release_sort/2, - ec_dictionary:to_list(rcl_state:configured_releases(State0))) of + ec_dictionary:to_list(rlx_state:configured_releases(State0))) of [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), + State1 = rlx_state:default_configured_release(State0, RelName, RelVsn), solve_release(State1, DepGraph, RelName, RelVsn); [] -> - ?RCL_ERROR(no_releases_in_system) + ?RLX_ERROR(no_releases_in_system) end. resolve_default_version(State0, DepGraph, RelName) -> %% Here we will just get the lastest version and run that. - AllReleases = ec_dictionary:to_list(rcl_state:configured_releases(State0)), + AllReleases = ec_dictionary:to_list(rlx_state:configured_releases(State0)), SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases, PossibleRelName =:= RelName], case lists:sort(fun release_sort/2, SpecificReleases) of [{{RelName, RelVsn}, _} | _] -> - State1 = rcl_state:default_configured_release(State0, RelName, RelVsn), + State1 = rlx_state:default_configured_release(State0, RelName, RelVsn), solve_release(State1, DepGraph, RelName, RelVsn); [] -> - ?RCL_ERROR({no_releases_for, RelName}) + ?RLX_ERROR({no_releases_for, RelName}) end. --spec release_sort({{rcl_release:name(),rcl_release:vsn()}, term()}, - {{rcl_release:name(),rcl_release:vsn()}, term()}) -> +-spec release_sort({{rlx_release:name(),rlx_release:vsn()}, term()}, + {{rlx_release:name(),rlx_release:vsn()}, term()}) -> boolean(). release_sort({{RelName, RelVsnA}, _}, {{RelName, RelVsnB}, _}) -> @@ -139,42 +139,42 @@ release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) -> ec_semver:lte(RelVsnA, RelVsnB). solve_release(State0, DepGraph, RelName, RelVsn) -> - rcl_log:debug(rcl_state:log(State0), + rlx_log:debug(rlx_state:log(State0), "Solving Release ~p-~s~n", [RelName, RelVsn]), try - Release = rcl_state:get_configured_release(State0, RelName, RelVsn), - Goals = rcl_release:goals(Release), + Release = rlx_state:get_configured_release(State0, RelName, RelVsn), + Goals = rlx_release:goals(Release), case Goals of [] -> - ?RCL_ERROR(no_goals_specified); + ?RLX_ERROR(no_goals_specified); _ -> - case rcl_depsolver:solve(DepGraph, Goals) of + case rlx_depsolver:solve(DepGraph, Goals) of {ok, Pkgs} -> set_resolved(State0, Release, Pkgs); {error, Error} -> - ?RCL_ERROR({failed_solve, Error}) + ?RLX_ERROR({failed_solve, Error}) end end catch throw:not_found -> - ?RCL_ERROR({release_not_found, RelName, RelVsn}) + ?RLX_ERROR({release_not_found, RelName, RelVsn}) end. set_resolved(State, Release0, Pkgs) -> - case rcl_release:realize(Release0, Pkgs, rcl_state:available_apps(State)) of + case rlx_release:realize(Release0, Pkgs, rlx_state:available_apps(State)) of {ok, Release1} -> - rcl_log:info(rcl_state:log(State), + rlx_log:info(rlx_state:log(State), "Resolved ~p-~s~n", - [rcl_release:name(Release1), - rcl_release:vsn(Release1)]), - rcl_log:debug(rcl_state:log(State), + [rlx_release:name(Release1), + rlx_release:vsn(Release1)]), + rlx_log:debug(rlx_state:log(State), fun() -> - rcl_release:format(1, Release1) + rlx_release:format(1, Release1) end), - {ok, rcl_state:add_realized_release(State, Release1)}; + {ok, rlx_state:add_realized_release(State, Release1)}; {error, E} -> - ?RCL_ERROR({release_error, E}) + ?RLX_ERROR({release_error, E}) end. %%%=================================================================== diff --git a/src/rlx_rel_discovery.erl b/src/rlx_rel_discovery.erl index 6cd84f0..53b329f 100644 --- a/src/rlx_rel_discovery.erl +++ b/src/rlx_rel_discovery.erl @@ -21,13 +21,13 @@ %%% @doc This provider uses the lib_dir setting of the state. It searches the %%% Lib Dirs looking for all OTP Applications that are available. When it finds %%% those OTP Applications it loads the information about them and adds them to -%%% the state of available apps. This implements the rcl_provider behaviour. --module(rcl_rel_discovery). +%%% the state of available apps. This implements the rlx_provider behaviour. +-module(rlx_rel_discovery). -export([do/3, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%============================================================================ %% API @@ -35,13 +35,13 @@ %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications --spec do(rcl_state:t(), [filename:name()], [rcl_app_info:t()]) -> - {ok, [rcl_release:t()]} | relcool:error(). +-spec do(rlx_state:t(), [filename:name()], [rlx_app_info:t()]) -> + {ok, [rlx_release:t()]} | relx:error(). do(State, LibDirs, AppMeta) -> - rcl_log:info(rcl_state:log(State), + rlx_log:info(rlx_state:log(State), fun() -> ["Resolving available releases from directories:\n", - [[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] + [[rlx_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]] end), resolve_rel_metadata(State, LibDirs, AppMeta). @@ -54,7 +54,7 @@ format_error(ErrorDetails) %%% Internal Functions %%%=================================================================== resolve_rel_metadata(State, LibDirs, AppMeta) -> - ReleaseMeta0 = lists:flatten(rcl_dscv_util:do(fun(LibDir, FileType) -> + ReleaseMeta0 = lists:flatten(rlx_dscv_util:do(fun(LibDir, FileType) -> discover_dir(LibDir, AppMeta, FileType) @@ -75,14 +75,14 @@ resolve_rel_metadata(State, LibDirs, AppMeta) -> case Errors of [] -> ReleaseMeta1 = [RelMeta || {ok, RelMeta} <- ReleaseMeta0], - rcl_log:debug(rcl_state:log(State), + rlx_log:debug(rlx_state:log(State), fun() -> ["Resolved the following OTP Releases from the system: \n", - [[rcl_release:format(1, Rel), "\n"] || Rel <- ReleaseMeta1]] + [[rlx_release:format(1, Rel), "\n"] || Rel <- ReleaseMeta1]] end), {ok, ReleaseMeta1}; _ -> - ?RCL_ERROR(Errors) + ?RLX_ERROR(Errors) end. -spec format_detail(ErrorDetail::term()) -> iolist(). @@ -91,8 +91,8 @@ format_detail({accessing, File, eaccess}) -> format_detail({accessing, File, Type}) -> io_lib:format("error (~p) accessing file ~s", [Type, File]). --spec discover_dir(file:name(), [rcl_app_info:t()], directory | file) -> - {ok, rcl_release:t()} +-spec discover_dir(file:name(), [rlx_app_info:t()], directory | file) -> + {ok, rlx_release:t()} | {error, Reason::term()} | {noresult, false}. discover_dir(_File, _AppMeta, directory) -> @@ -101,8 +101,8 @@ discover_dir(File, AppMeta, file) -> is_valid_release(File, AppMeta). -spec is_valid_release(file:name(), - [rcl_app_info:t()]) -> - {ok, rcl_release:t()} + [rlx_app_info:t()]) -> + {ok, rlx_release:t()} | {error, Reason::term()} | {noresult, false}. is_valid_release(File, AppMeta) -> @@ -120,18 +120,18 @@ resolve_release(RelFile, AppMeta) -> Apps}]} -> build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta); {ok, InvalidRelease} -> - ?RCL_ERROR({invalid_release_information, InvalidRelease}); + ?RLX_ERROR({invalid_release_information, InvalidRelease}); {error, Reason} -> - ?RCL_ERROR({unable_to_read, RelFile, Reason}) + ?RLX_ERROR({unable_to_read, RelFile, Reason}) end. build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> - Release = rcl_release:erts(rcl_release:new(RelName, RelVsn, RelFile), + Release = rlx_release:erts(rlx_release:new(RelName, RelVsn, RelFile), ErtsVsn), resolve_apps(Apps, AppMeta, Release, []). resolve_apps([], _AppMeta, Release, Acc) -> - {ok, rcl_release:application_details(Release, Acc)}; + {ok, rlx_release:application_details(Release, Acc)}; resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> AppName = erlang:element(1, AppInfo), AppVsn = ec_semver:parse(erlang:element(2, AppInfo)), @@ -144,13 +144,13 @@ resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> find_app(AppName, AppVsn, AppMeta) -> case ec_lists:find(fun(App) -> - NAppName = rcl_app_info:name(App), - NAppVsn = rcl_app_info:vsn(App), + NAppName = rlx_app_info:name(App), + NAppVsn = rlx_app_info:vsn(App), AppName == NAppName andalso AppVsn == NAppVsn end, AppMeta) of {ok, Head} -> Head; error -> - ?RCL_ERROR({could_not_find, {AppName, AppVsn}}) + ?RLX_ERROR({could_not_find, {AppName, AppVsn}}) end. diff --git a/src/rlx_release.erl b/src/rlx_release.erl index 9ed741e..68193fa 100644 --- a/src/rlx_release.erl +++ b/src/rlx_release.erl @@ -20,7 +20,7 @@ %%% %%% @doc This module represents a release and its metadata and is used to %%% manipulate the release metadata. --module(rcl_release). +-module(rlx_release). -export([new/2, new/3, @@ -52,17 +52,17 @@ application_spec/0, application_goal/0]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). -record(release_t, {name :: atom(), vsn :: ec_semver:any_version(), erts :: ec_semver:any_version(), - goals = [] :: [rcl_depsolver:constraint()], + goals = [] :: [rlx_depsolver:constraint()], realized = false :: boolean(), annotations = undefined :: annotations(), applications = [] :: [application_spec()], relfile :: undefined | string(), - app_detail = [] :: [rcl_app_info:t()]}). + app_detail = [] :: [rlx_app_info:t()]}). %%============================================================================ %% types @@ -78,7 +78,7 @@ {app_name(), app_vsn(), app_type() | incl_apps()} | {app_name(), app_vsn(), app_type(), incl_apps()}. --type application_constraint() :: rcl_depsolver:raw_constraint() | string() | binary(). +-type application_constraint() :: rlx_depsolver:raw_constraint() | string() | binary(). -type application_goal() :: application_constraint() | {application_constraint(), app_type() | incl_apps()} | {application_constraint(), app_type(), incl_apps() | none}. @@ -127,7 +127,7 @@ erts(Release, Vsn) -> erts(#release_t{erts=Vsn}) -> Vsn. --spec goals(t(), [application_goal()]) -> {ok, t()} | relcool:error(). +-spec goals(t(), [application_goal()]) -> {ok, t()} | relx:error(). goals(Release, Goals0) -> lists:foldl(fun parse_goal0/2, {ok, Release}, Goals0). @@ -136,11 +136,11 @@ goals(Release, Goals0) -> goals(#release_t{goals=Goals}) -> Goals. --spec realize(t(), [{app_name(), app_vsn()}], [rcl_app_info:t()]) -> - {ok, t()} | relcool:error(). +-spec realize(t(), [{app_name(), app_vsn()}], [rlx_app_info:t()]) -> + {ok, t()} | relx:error(). realize(Rel, Pkgs0, World0) -> World1 = subset_world(Pkgs0, World0), - case rcl_topo:sort_apps(World1) of + case rlx_topo:sort_apps(World1) of {ok, Pkgs1} -> process_specs(realize_erts(Rel), Pkgs1); Error={error, _} -> @@ -153,16 +153,16 @@ realize(Rel, Pkgs0, World0) -> applications(#release_t{applications=Apps}) -> Apps. -%% @doc this gives the rcl_app_info objects representing the applications in +%% @doc this gives the rlx_app_info objects representing the applications in %% this release. These should only be populated by the 'realize' call in this %% module or by reading an existing rel file. --spec application_details(t()) -> [rcl_app_info:t()]. +-spec application_details(t()) -> [rlx_app_info:t()]. application_details(#release_t{app_detail=App}) -> App. %% @doc this is only expected to be called by a process building a new release %% from an existing rel file. --spec application_details(t(), [rcl_app_info:t()]) -> t(). +-spec application_details(t(), [rlx_app_info:t()]) -> t(). application_details(Release, AppDetail) -> Release#release_t{app_detail=AppDetail}. @@ -178,7 +178,7 @@ metadata(#release_t{name=Name, vsn=Vsn, erts=ErtsVsn, applications=Apps, {ok, {release, {erlang:atom_to_list(Name), Vsn}, {erts, ErtsVsn}, Apps}}; false -> - ?RCL_ERROR({not_realized, Name, Vsn}) + ?RLX_ERROR({not_realized, Name, Vsn}) end. %% @doc produce the canonical name (-) for this release @@ -194,16 +194,16 @@ format(Release) -> -spec format(non_neg_integer(), t()) -> iolist(). format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, goals = Goals, applications=Apps}) -> - BaseIndent = rcl_util:indent(Indent), - [BaseIndent, "release: ", rcl_util:to_string(Name), "-", Vsn, "\n", - rcl_util:indent(Indent + 1), " erts-", ErtsVsn, + BaseIndent = rlx_util:indent(Indent), + [BaseIndent, "release: ", rlx_util:to_string(Name), "-", Vsn, "\n", + rlx_util:indent(Indent + 1), " erts-", ErtsVsn, ", realized = ", erlang:atom_to_list(Realized), "\n", BaseIndent, "goals: \n", - [[rcl_util:indent(Indent + 1), format_goal(Goal), ",\n"] || Goal <- Goals], + [[rlx_util:indent(Indent + 1), format_goal(Goal), ",\n"] || Goal <- Goals], case Realized of true -> [BaseIndent, "applications: \n", - [[rcl_util:indent(Indent + 1), io_lib:format("~p", [App]), ",\n"] || + [[rlx_util:indent(Indent + 1), io_lib:format("~p", [App]), ",\n"] || App <- Apps]]; false -> [] @@ -211,15 +211,15 @@ format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, -spec format_goal(application_goal()) -> iolist(). format_goal({Constraint, AppType}) -> - io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType}]); + io_lib:format("~p", [{rlx_depsolver:format_constraint(Constraint), AppType}]); format_goal({Constraint, AppType, AppInc}) -> - io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType, AppInc}]); + io_lib:format("~p", [{rlx_depsolver:format_constraint(Constraint), AppType, AppInc}]); format_goal(Constraint) -> - rcl_depsolver:format_constraint(Constraint). + rlx_depsolver:format_constraint(Constraint). -spec format_error(Reason::term()) -> iolist(). format_error({topo_error, E}) -> - rcl_topo:format_error(E); + rlx_topo:format_error(E); format_error({failed_to_parse, Con}) -> io_lib:format("Failed to parse constraint ~p", [Con]); format_error({invalid_constraint, _, Con}) -> @@ -237,30 +237,30 @@ realize_erts(Rel=#release_t{erts=undefined}) -> realize_erts(Rel) -> Rel. --spec process_specs(t(), [rcl_app_info:t()]) -> +-spec process_specs(t(), [rlx_app_info:t()]) -> {ok, t()}. process_specs(Rel=#release_t{annotations=Annots, goals=Goals}, World) -> - ActiveApps = lists:flatten([rcl_app_info:active_deps(El) || El <- World] ++ + ActiveApps = lists:flatten([rlx_app_info:active_deps(El) || El <- World] ++ [case get_app_name(Goal) of {error, _} -> []; G -> G end || Goal <- Goals]), - LibraryApps = lists:flatten([rcl_app_info:library_deps(El) || El <- World]), + LibraryApps = lists:flatten([rlx_app_info:library_deps(El) || El <- World]), Specs = [create_app_spec(Annots, App, ActiveApps, LibraryApps) || App <- World], {ok, Rel#release_t{annotations=Annots, applications=Specs, app_detail=World, realized=true}}. --spec create_app_spec(annotations(), rcl_app_info:t(), [app_name()], +-spec create_app_spec(annotations(), rlx_app_info:t(), [app_name()], [app_name()]) -> application_spec(). create_app_spec(Annots, App, ActiveApps, LibraryApps) -> %% If the app only exists as a dependency in a library app then it should %% get the 'load' annotation unless the release spec has provided something %% else - AppName = rcl_app_info:name(App), + AppName = rlx_app_info:name(App), TypeAnnot = case (lists:member(AppName, LibraryApps) and (not lists:member(AppName, ActiveApps))) of @@ -281,7 +281,7 @@ create_app_spec(Annots, App, ActiveApps, LibraryApps) -> throw:not_found -> {TypeAnnot, none} end, - Vsn = rcl_app_info:vsn_as_string(App), + Vsn = rlx_app_info:vsn_as_string(App), case BaseAnnots of {none, none} -> {AppName, Vsn}; @@ -293,16 +293,16 @@ create_app_spec(Annots, App, ActiveApps, LibraryApps) -> {AppName, Vsn, Type, Incld1} end. --spec subset_world([{app_name(), app_vsn()}], [rcl_app_info:t()]) -> [rcl_app_info:t()]. +-spec subset_world([{app_name(), app_vsn()}], [rlx_app_info:t()]) -> [rlx_app_info:t()]. subset_world(Pkgs, World) -> [get_app_info(Pkg, World) || Pkg <- Pkgs]. --spec get_app_info({app_name(), app_vsn()}, [rcl_app_info:t()]) -> rcl_app_info:t(). +-spec get_app_info({app_name(), app_vsn()}, [rlx_app_info:t()]) -> rlx_app_info:t(). get_app_info({PkgName, PkgVsn}, World) -> {ok, WorldEl} = ec_lists:find(fun(El) -> - rcl_app_info:name(El) =:= PkgName andalso - rcl_app_info:vsn(El) =:= PkgVsn + rlx_app_info:name(El) =:= PkgName andalso + rlx_app_info:vsn(El) =:= PkgVsn end, World), WorldEl. @@ -341,7 +341,7 @@ parse_goal0(Constraint0, {ok, Release}) -> parse_goal0(_, E = {error, _}) -> E; parse_goal0(Constraint, _) -> - ?RCL_ERROR({invalid_constraint, 1, Constraint}). + ?RLX_ERROR({invalid_constraint, 1, Constraint}). parse_goal1(Release = #release_t{annotations=Annots, goals=Goals}, Constraint, NewAnnots) -> @@ -355,12 +355,12 @@ parse_goal1(Release = #release_t{annotations=Annots, goals=Goals}, end. -spec parse_constraint(application_constraint()) -> - rcl_depsolver:constraint() | relcool:error(). + rlx_depsolver:constraint() | relx:error(). parse_constraint(Constraint0) when erlang:is_list(Constraint0); erlang:is_binary(Constraint0) -> - case rcl_goal:parse(Constraint0) of + case rlx_goal:parse(Constraint0) of {fail, _} -> - ?RCL_ERROR({failed_to_parse, Constraint0}); + ?RLX_ERROR({failed_to_parse, Constraint0}); {ok, Constraint1} -> {ok, Constraint1} end; @@ -368,17 +368,17 @@ parse_constraint(Constraint0) when erlang:is_tuple(Constraint0); erlang:is_atom(Constraint0) -> Constraint1 = parse_version(Constraint0), - case rcl_depsolver:is_valid_constraint(Constraint1) of + case rlx_depsolver:is_valid_constraint(Constraint1) of false -> - ?RCL_ERROR({invalid_constraint, 2, Constraint0}); + ?RLX_ERROR({invalid_constraint, 2, Constraint0}); true -> {ok, Constraint1} end; parse_constraint(Constraint) -> - ?RCL_ERROR({invalid_constraint, 3, Constraint}). + ?RLX_ERROR({invalid_constraint, 3, Constraint}). --spec get_app_name(rcl_depsolver:raw_constraint()) -> - AppName::atom() | relcool:error(). +-spec get_app_name(rlx_depsolver:raw_constraint()) -> + AppName::atom() | relx:error(). get_app_name(AppName) when erlang:is_atom(AppName) -> AppName; get_app_name({AppName, _}) when erlang:is_atom(AppName) -> @@ -388,22 +388,22 @@ get_app_name({AppName, _, _}) when erlang:is_atom(AppName) -> get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) -> AppName; get_app_name(V) -> - ?RCL_ERROR({invalid_constraint, 4, V}). + ?RLX_ERROR({invalid_constraint, 4, V}). --spec parse_version(rcl_depsolver:raw_constraint()) -> - rcl_depsolver:constraint(). +-spec parse_version(rlx_depsolver:raw_constraint()) -> + rlx_depsolver:constraint(). parse_version({AppName, Version}) when erlang:is_binary(Version); erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version)}; + {AppName, rlx_depsolver:parse_version(Version)}; parse_version({AppName, Version, Constraint}) when erlang:is_binary(Version); erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version), Constraint}; + {AppName, rlx_depsolver:parse_version(Version), Constraint}; parse_version({AppName, Version, Constraint0, Constraint1}) when erlang:is_binary(Version); erlang:is_list(Version) -> - {AppName, rcl_depsolver:parse_version(Version), Constraint1, Constraint0}; + {AppName, rlx_depsolver:parse_version(Version), Constraint1, Constraint0}; parse_version(Constraint) -> Constraint. diff --git a/src/rlx_state.erl b/src/rlx_state.erl index ca6ec8c..9b0811f 100644 --- a/src/rlx_state.erl +++ b/src/rlx_state.erl @@ -18,10 +18,10 @@ %%% @author Eric Merritt %%% @copyright (C) 2012 Erlware, LLC. %%% -%%% @doc Provides state management services for the relcool tool. Generally, +%%% @doc Provides state management services for the relx tool. Generally, %%% those things that are fixed have a direct api. Those things that are mutable %%% have a more mutable api. --module(rcl_state). +-module(rlx_state). -export([new/2, log/1, @@ -67,17 +67,17 @@ releases/0, cmd_args/0]). --record(state_t, {log :: rcl_log:t(), +-record(state_t, {log :: rlx_log:t(), root_dir :: file:name(), caller :: caller(), action :: atom(), output_dir :: file:name(), lib_dirs=[] :: [file:name()], config_file=[] :: file:filename() | undefined, - goals=[] :: [rcl_depsolver:constraint()], - providers = [] :: [rcl_provider:t()], - available_apps = [] :: [rcl_app_info:t()], - default_configured_release :: {rcl_release:name(), rcl_release:vsn()}, + goals=[] :: [rlx_depsolver:constraint()], + providers = [] :: [rlx_provider:t()], + available_apps = [] :: [rlx_app_info:t()], + default_configured_release :: {rlx_release:name(), rlx_release:vsn()}, sys_config :: file:filename() | undefined, overrides :: [{AppName::atom(), Directory::file:filename()}], skip_apps = [] :: [AppName::atom()], @@ -91,9 +91,9 @@ %% types %%============================================================================ --type releases() :: ec_dictionary:dictionary({rcl_release:name(), - rcl_release:vsn()}, - rcl_release:t()). +-type releases() :: ec_dictionary:dictionary({rlx_release:name(), + rlx_release:vsn()}, + rlx_release:t()). -type cmd_args() :: proplists:proplist(). -type caller() :: command_line | api. @@ -109,7 +109,7 @@ new(PropList, Target) erlang:is_atom(Target) -> {ok, Root} = file:get_cwd(), State0 = - #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), + #state_t{log = proplists:get_value(log, PropList, rlx_log:new(error)), output_dir=proplists:get_value(output_dir, PropList, ""), lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], config_file=proplists:get_value(config, PropList, undefined), @@ -125,7 +125,7 @@ new(PropList, Target) upfrom = proplists:get_value(upfrom, PropList, undefined), default_configured_release={proplists:get_value(relname, PropList, undefined), proplists:get_value(relvsn, PropList, undefined)}}, - rcl_state:put(create_logic_providers(State0), + rlx_state:put(create_logic_providers(State0), disable_default_libs, proplists:get_value(disable_default_libs, PropList, false)). @@ -155,7 +155,7 @@ skip_apps(State, SkipApps) -> State#state_t{skip_apps=SkipApps}. %% @doc get the current log state for the system --spec log(t()) -> rcl_log:t(). +-spec log(t()) -> rlx_log:t(). log(#state_t{log=LogState}) -> LogState. @@ -167,7 +167,7 @@ output_dir(#state_t{output_dir=OutDir}) -> lib_dirs(#state_t{lib_dirs=LibDir}) -> LibDir. --spec goals(t()) -> [rcl_depsolver:constraint()]. +-spec goals(t()) -> [rlx_depsolver:constraint()]. goals(#state_t{goals=TS}) -> TS. @@ -179,7 +179,7 @@ config_file(#state_t{config_file=ConfigFiles}) -> config_file(State, ConfigFiles) -> State#state_t{config_file=ConfigFiles}. --spec providers(t()) -> [rcl_provider:t()]. +-spec providers(t()) -> [rlx_provider:t()]. providers(#state_t{providers=Providers}) -> Providers. @@ -199,18 +199,18 @@ root_dir(#state_t{root_dir=RootDir}) -> root_dir(State, RootDir) -> State#state_t{root_dir=RootDir}. --spec providers(t(), [rcl_provider:t()]) -> t(). +-spec providers(t(), [rlx_provider:t()]) -> t(). providers(M, NewProviders) -> M#state_t{providers=NewProviders}. --spec add_configured_release(t(), rcl_release:t()) -> t(). +-spec add_configured_release(t(), rlx_release:t()) -> t(). add_configured_release(M=#state_t{configured_releases=Releases}, Release) -> - M#state_t{configured_releases=ec_dictionary:add({rcl_release:name(Release), - rcl_release:vsn(Release)}, + M#state_t{configured_releases=ec_dictionary:add({rlx_release:name(Release), + rlx_release:vsn(Release)}, Release, Releases)}. --spec get_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +-spec get_configured_release(t(), rlx_release:name(), rlx_release:vsn()) -> rlx_release:t(). get_configured_release(#state_t{configured_releases=Releases}, Name, Vsn) -> ec_dictionary:get({Name, Vsn}, Releases). @@ -226,38 +226,38 @@ realized_releases(#state_t{realized_releases=Releases}) -> realized_releases(State, Releases) -> State#state_t{realized_releases=Releases}. --spec add_realized_release(t(), rcl_release:t()) -> t(). +-spec add_realized_release(t(), rlx_release:t()) -> t(). add_realized_release(State = #state_t{realized_releases=Releases}, Release) -> - NewReleases = ec_dictionary:add({rcl_release:name(Release), rcl_release:vsn(Release)}, + NewReleases = ec_dictionary:add({rlx_release:name(Release), rlx_release:vsn(Release)}, Release, Releases), State#state_t{realized_releases=NewReleases}. --spec get_realized_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +-spec get_realized_release(t(), rlx_release:name(), rlx_release:vsn()) -> rlx_release:t(). get_realized_release(#state_t{realized_releases=Releases}, Name, Vsn) -> ec_dictionary:get({Name, Vsn}, Releases). --spec update_realized_release(t(), rcl_release:t()) -> +-spec update_realized_release(t(), rlx_release:t()) -> t(). update_realized_release(M=#state_t{realized_releases=Releases}, Release) -> - M#state_t{realized_releases=ec_dictionary:add({rcl_release:name(Release), - rcl_release:vsn(Release)}, + M#state_t{realized_releases=ec_dictionary:add({rlx_release:name(Release), + rlx_release:vsn(Release)}, Release, Releases)}. -spec default_configured_release(t()) -> - {rcl_release:name() | undefined, rcl_release:vsn() | undefined}. + {rlx_release:name() | undefined, rlx_release:vsn() | undefined}. default_configured_release(#state_t{default_configured_release=Def}) -> Def. --spec default_configured_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). +-spec default_configured_release(t(), rlx_release:name(), rlx_release:vsn()) -> t(). default_configured_release(M, Name, Vsn) -> M#state_t{default_configured_release={Name, Vsn}}. --spec available_apps(t()) -> [rcl_app_info:t()]. +-spec available_apps(t()) -> [rlx_app_info:t()]. available_apps(#state_t{available_apps=Apps}) -> Apps. --spec available_apps(t(), [rcl_app_info:t()]) -> t(). +-spec available_apps(t(), [rlx_app_info:t()]) -> t(). available_apps(M, NewApps) -> M#state_t{available_apps=NewApps}. @@ -304,19 +304,19 @@ format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, providers=Providers}, Indent) -> Values1 = ec_dictionary:to_list(Values0), - [rcl_util:indent(Indent), + [rlx_util:indent(Indent), <<"state(">>, erlang:atom_to_list(Caller), <<"):\n">>, - rcl_util:indent(Indent + 1), <<"log: ">>, rcl_log:format(LogState), <<",\n">>, - rcl_util:indent(Indent + 1), "config file: ", rcl_util:optional_to_string(ConfigFile), "\n", - rcl_util:indent(Indent + 1), "goals: \n", - [[rcl_util:indent(Indent + 2), rcl_depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], - rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", - rcl_util:indent(Indent + 1), "lib_dirs: \n", - [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs], - rcl_util:indent(Indent + 1), "providers: \n", - [[rcl_util:indent(Indent + 2), rcl_provider:format(Provider), ",\n"] || Provider <- Providers], - rcl_util:indent(Indent + 1), "provider config values: \n", - [[rcl_util:indent(Indent + 2), io_lib:format("~p", [Value]), ",\n"] || Value <- Values1]]. + rlx_util:indent(Indent + 1), <<"log: ">>, rlx_log:format(LogState), <<",\n">>, + rlx_util:indent(Indent + 1), "config file: ", rlx_util:optional_to_string(ConfigFile), "\n", + rlx_util:indent(Indent + 1), "goals: \n", + [[rlx_util:indent(Indent + 2), rlx_depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], + rlx_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", + rlx_util:indent(Indent + 1), "lib_dirs: \n", + [[rlx_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs], + rlx_util:indent(Indent + 1), "providers: \n", + [[rlx_util:indent(Indent + 2), rlx_provider:format(Provider), ",\n"] || Provider <- Providers], + rlx_util:indent(Indent + 1), "provider config values: \n", + [[rlx_util:indent(Indent + 2), io_lib:format("~p", [Value]), ",\n"] || Value <- Values1]]. %%%=================================================================== %%% Internal Functions @@ -324,11 +324,11 @@ format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, -spec create_logic_providers(t()) -> t(). create_logic_providers(State0) -> - {ConfigProvider, {ok, State1}} = rcl_provider:new(rcl_prv_config, State0), - {DiscoveryProvider, {ok, State2}} = rcl_provider:new(rcl_prv_discover, State1), - {ReleaseProvider, {ok, State3}} = rcl_provider:new(rcl_prv_release, State2), - {OverlayProvider, {ok, State4}} = rcl_provider:new(rcl_prv_overlay, State3), - {AssemblerProvider, {ok, State5}} = rcl_provider:new(rcl_prv_assembler, State4), + {ConfigProvider, {ok, State1}} = rlx_provider:new(rlx_prv_config, State0), + {DiscoveryProvider, {ok, State2}} = rlx_provider:new(rlx_prv_discover, State1), + {ReleaseProvider, {ok, State3}} = rlx_provider:new(rlx_prv_release, State2), + {OverlayProvider, {ok, State4}} = rlx_provider:new(rlx_prv_overlay, State3), + {AssemblerProvider, {ok, State5}} = rlx_provider:new(rlx_prv_assembler, State4), State5#state_t{providers=[ConfigProvider, DiscoveryProvider, ReleaseProvider, OverlayProvider, AssemblerProvider]}. @@ -347,7 +347,7 @@ to_binary(Dir) -include_lib("eunit/include/eunit.hrl"). new_test() -> - LogState = rcl_log:new(error), + LogState = rlx_log:new(error), RCLState = new([{log, LogState}], release), ?assertMatch(LogState, log(RCLState)). diff --git a/src/rlx_topo.erl b/src/rlx_topo.erl index 462b7c5..11928c1 100644 --- a/src/rlx_topo.erl +++ b/src/rlx_topo.erl @@ -20,7 +20,7 @@ %%% @doc %%% This is a pretty simple topological sort for erlang. It was %%% originally written for ermake by Joe Armstrong back in '98. It -%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relcool. +%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relx. %%% %%% A partial order on the set S is a set of pairs {Xi,Xj} such that %%% some relation between Xi and Xj is obeyed. @@ -30,12 +30,12 @@ %%% order i < j %%% @end %%%------------------------------------------------------------------- --module(rcl_topo). +-module(rlx_topo). -export([sort_apps/1, format_error/1]). --include_lib("relcool/include/relcool.hrl"). +-include_lib("relx/include/relx.hrl"). %%==================================================================== %% Types @@ -53,9 +53,9 @@ %% applications. This implies that you have already done the %% constraint solve before you pass the list of apps here to be %% sorted. --spec sort_apps([rcl_app_info:t()]) -> - {ok, [rcl_app_info:t()]} | - relcool:error(). +-spec sort_apps([rlx_app_info:t()]) -> + {ok, [rlx_app_info:t()]} | + relx:error(). sort_apps(Apps) -> Pairs = apps_to_pairs(Apps), case sort(Pairs) of @@ -71,9 +71,9 @@ format_error({cycle, Pairs}) -> "before we can continue:\n", case Pairs of [{P1, P2}] -> - [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1)]; + [rlx_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1)]; [{P1, P2} | Rest] -> - [rcl_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1), + [rlx_util:indent(1), erlang:atom_to_list(P2), "->", erlang:atom_to_list(P1), [["-> ", erlang:atom_to_list(PP2), " -> ", erlang:atom_to_list(PP1)] || {PP1, PP2} <- Rest]]; [] -> [] @@ -83,43 +83,43 @@ format_error({cycle, Pairs}) -> %% Internal Functions %%==================================================================== %% @doc Do a topological sort on the list of pairs. --spec sort([pair()]) -> {ok, [atom()]} | relcool:error(). +-spec sort([pair()]) -> {ok, [atom()]} | relx:error(). sort(Pairs) -> iterate(Pairs, [], all(Pairs)). --spec names_to_apps([atom()], [rcl_app_info:t()]) -> [rcl_app_info:t()]. +-spec names_to_apps([atom()], [rlx_app_info:t()]) -> [rlx_app_info:t()]. names_to_apps(Names, Apps) -> [find_app_by_name(Name, Apps) || Name <- Names]. --spec find_app_by_name(atom(), [rcl_app_info:t()]) -> rcl_app_info:t(). +-spec find_app_by_name(atom(), [rlx_app_info:t()]) -> rlx_app_info:t(). find_app_by_name(Name, Apps) -> {ok, App1} = ec_lists:find(fun(App) -> - rcl_app_info:name(App) =:= Name + rlx_app_info:name(App) =:= Name end, Apps), App1. --spec apps_to_pairs([rcl_app_info:t()]) -> [pair()]. +-spec apps_to_pairs([rlx_app_info:t()]) -> [pair()]. apps_to_pairs(Apps) -> lists:flatten([app_to_pairs(App) || App <- Apps]). --spec app_to_pairs(rcl_app_info:t()) -> [pair()]. +-spec app_to_pairs(rlx_app_info:t()) -> [pair()]. app_to_pairs(App) -> - [{DepApp, rcl_app_info:name(App)} || + [{DepApp, rlx_app_info:name(App)} || DepApp <- - rcl_app_info:active_deps(App) ++ - rcl_app_info:library_deps(App)]. + rlx_app_info:active_deps(App) ++ + rlx_app_info:library_deps(App)]. %% @doc Iterate over the system. @private -spec iterate([pair()], [name()], [name()]) -> - {ok, [name()]} | relcool:error(). + {ok, [name()]} | relx:error(). iterate([], L, All) -> {ok, remove_duplicates(L ++ subtract(All, L))}; iterate(Pairs, L, All) -> case subtract(lhs(Pairs), rhs(Pairs)) of [] -> - ?RCL_ERROR({cycle, Pairs}); + ?RLX_ERROR({cycle, Pairs}); Lhs -> iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) end. @@ -193,8 +193,8 @@ topo_pairs_cycle_test() -> sort(Pairs)). topo_apps_cycle_test() -> - {ok, App1} = rcl_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]), - {ok, App2} = rcl_app_info:new(app2, "0.1", "/no-dir", [app1], []), + {ok, App1} = rlx_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]), + {ok, App2} = rlx_app_info:new(app2, "0.1", "/no-dir", [app1], []), Apps = [App1, App2], ?assertMatch({error, {_, {cycle, [{app2,app1},{app1,app2}]}}}, sort_apps(Apps)). @@ -202,16 +202,16 @@ topo_apps_cycle_test() -> topo_apps_good_test() -> Apps = [App || {ok, App} <- - [rcl_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]), - rcl_app_info:new(app2, "0.1", "/no-dir", [app3], []), - rcl_app_info:new(app3, "0.1", "/no-dir", [kernel], []), - rcl_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []), - rcl_app_info:new(stdlib, "0.1", "/no-dir", [], []), - rcl_app_info:new(kernel, "0.1", "/no-dir", [], []), - rcl_app_info:new(zapp2, "0.1", "/no-dir", [], [])]], + [rlx_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]), + rlx_app_info:new(app2, "0.1", "/no-dir", [app3], []), + rlx_app_info:new(app3, "0.1", "/no-dir", [kernel], []), + rlx_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []), + rlx_app_info:new(stdlib, "0.1", "/no-dir", [], []), + rlx_app_info:new(kernel, "0.1", "/no-dir", [], []), + rlx_app_info:new(zapp2, "0.1", "/no-dir", [], [])]], {ok, Sorted} = sort_apps(Apps), ?assertMatch([stdlib, kernel, zapp2, app3, app2, zapp1, app1], - [rcl_app_info:name(App) || App <- Sorted]). + [rlx_app_info:name(App) || App <- Sorted]). -endif. diff --git a/src/rlx_util.erl b/src/rlx_util.erl index 4afb26d..c2b2081 100644 --- a/src/rlx_util.erl +++ b/src/rlx_util.erl @@ -19,7 +19,7 @@ %%% @copyright (C) 2012 Erlware, LLC. %%% %%% @doc Trivial utility file to help handle common tasks --module(rcl_util). +-module(rlx_util). -export([mkdir_p/1, to_binary/1, @@ -66,12 +66,12 @@ to_string(Atom) when erlang:is_atom(Atom) -> to_string(Else) when erlang:is_list(Else) -> Else. -%% @doc get the reason for a particular relcool error --spec error_reason(relcool:error()) -> any(). +%% @doc get the reason for a particular relx error +-spec error_reason(relx:error()) -> any(). error_reason({error, {_, Reason}}) -> Reason. -%% @doc check to see if the value is a relcool error --spec is_error(relcool:error() | any()) -> boolean(). +%% @doc check to see if the value is a relx error +-spec is_error(relx:error() | any()) -> boolean(). is_error({error, _}) -> true; is_error(_) -> diff --git a/test/rlx_command_SUITE.erl b/test/rlx_command_SUITE.erl index 05da548..6f726b4 100644 --- a/test/rlx_command_SUITE.erl +++ b/test/rlx_command_SUITE.erl @@ -17,7 +17,7 @@ %%%------------------------------------------------------------------- %%% @author Eric Merrit %%% @copyright (C) 2012, Eric Merrit --module(rclt_command_SUITE). +-module(rlx_command_SUITE). -export([suite/0, init_per_suite/1, @@ -48,8 +48,8 @@ normal_passing_case(Config) -> Lib1 = filename:join([DataDir, <<"lib1">>]), Lib2 = filename:join([DataDir, <<"lib2">>]), Outdir = filename:join([DataDir, "outdir"]), - ok = rcl_util:mkdir_p(Lib1), - ok = rcl_util:mkdir_p(Lib2), + ok = rlx_util:mkdir_p(Lib1), + ok = rlx_util:mkdir_p(Lib2), Goal1 = "app1<=33.33+build4", Goal2 = "app2:btwn:33.22,45.22+build.21", @@ -58,40 +58,40 @@ normal_passing_case(Config) -> RelVsn = "33.222", CmdLine = ["-V", LogLevel, "-g",Goal1,"-g",Goal2, "-l", Lib1, "-l", Lib2, "-n", RelName, "-v", RelVsn, "-o", Outdir], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), - {ok, State} = rcl_cmd_args:args2state(Opts, Targets), + {ok, {Opts, Targets}} = getopt:parse(relx:opt_spec_list(), CmdLine), + {ok, State} = rlx_cmd_args:args2state(Opts, Targets), ?assertMatch([Lib1, Lib2], - rcl_state:lib_dirs(State)), - ?assertMatch(Outdir, rcl_state:output_dir(State)), + rlx_state:lib_dirs(State)), + ?assertMatch(Outdir, rlx_state:output_dir(State)), ?assertMatch([{app1,{{33,33},{[],[<<"build4">>]}},lte}, {app2, {{33,22},{[],[]}}, {{45,22},{[],[<<"build">>,21]}}, between}], - rcl_state:goals(State)). + rlx_state:goals(State)). lib_fail_case(Config) -> DataDir = proplists:get_value(data_dir, Config), Lib1 = filename:join([DataDir, "lib1"]), Lib2 = filename:join([DataDir, "lib3333"]), - ok = rcl_util:mkdir_p(Lib1), + ok = rlx_util:mkdir_p(Lib1), CmdLine = ["-l", Lib1, "-l", Lib2], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, {Opts, Targets}} = getopt:parse(relx:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {not_directory, Lib2}}}, - rcl_cmd_args:args2state(Opts, Targets)). + rlx_cmd_args:args2state(Opts, Targets)). spec_parse_fail_case(_Config) -> Spec = "aaeu:3333:33.22a44", CmdLine = ["-g", Spec], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, {Opts, Targets}} = getopt:parse(relx:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {failed_to_parse, _Spec}}}, - rcl_cmd_args:args2state(Opts, Targets)). + rlx_cmd_args:args2state(Opts, Targets)). config_fail_case(_Config) -> ConfigFile = "does-not-exist", CmdLine = ["-c", ConfigFile], - {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, {Opts, Targets}} = getopt:parse(relx:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, - rcl_cmd_args:args2state(Opts, Targets)). + rlx_cmd_args:args2state(Opts, Targets)). diff --git a/test/rlx_depsolver_tester.erl b/test/rlx_depsolver_tester.erl index 53f5ac0..2dddf76 100644 --- a/test/rlx_depsolver_tester.erl +++ b/test/rlx_depsolver_tester.erl @@ -23,7 +23,7 @@ %% Additional testing for depsolver %% @end %%------------------------------------------------------------------- --module(rcl_depsolver_tester). +-module(rlx_depsolver_tester). -export([run_data/1, run_log/1]). -include_lib("eunit/include/eunit.hrl"). @@ -352,7 +352,7 @@ log_ea2d264b_test() -> versionify(X) when erlang:is_list(X) -> lists:map(fun versionify/1, X); versionify({K, V}) -> - {erlang:list_to_binary(K), rcl_depsolver:parse_version(V)}. + {erlang:list_to_binary(K), rlx_depsolver:parse_version(V)}. fix_rebar_brokenness(Filename) -> Alt1 = filename:join(["./test", "data", Filename]), @@ -372,7 +372,7 @@ fix_rebar_brokenness(Filename) -> run_data_file(Device) -> Constraints = get_constraints(io:get_line(Device, "")), - rcl_depsolver:solve(process_packages(read_packages(Device)), Constraints). + rlx_depsolver:solve(process_packages(read_packages(Device)), Constraints). goble_lines(_Device, eof, Acc) -> lists:reverse(Acc); @@ -385,14 +385,14 @@ goble_lines(Device) -> goble_lines(Device, io:get_line(Device, ""), []). run_log_file(Device) -> - State0 = rcl_depsolver:new_graph(), + State0 = rlx_depsolver:new_graph(), {Goals, State2} = lists:foldl(fun(Line, Data) -> process_add_goal(Line, process_add_constraint(Line, process_add_package(Line, Data))) end, {[], State0}, goble_lines(Device)), - rcl_depsolver:solve(State2, Goals). + rlx_depsolver:solve(State2, Goals). read_packages(Device) -> process_line(Device, io:get_line(Device, ""), []). @@ -424,8 +424,8 @@ process_line(Device, Pkg, Acc) -> process_packages(Pkgs) -> lists:foldl(fun({Pkg, Vsn, Constraints}, Dom0) -> - rcl_depsolver:add_package_version(Dom0, Pkg, Vsn, Constraints) - end, rcl_depsolver:new_graph(), Pkgs). + rlx_depsolver:add_package_version(Dom0, Pkg, Vsn, Constraints) + end, rlx_depsolver:new_graph(), Pkgs). get_constraints(ConLine) -> AppVsns = string:tokens(ConLine, " \n"), @@ -446,7 +446,7 @@ process_add_package(Line, {Goals, State0}) -> {match, [_All, _InstNumber, PkgName, _PkgCount, VersionCount]} -> {Goals, lists:foldl(fun(PkgVsn, State1) -> - rcl_depsolver:add_package_version(State1, + rlx_depsolver:add_package_version(State1, PkgName, erlang:integer_to_list(PkgVsn), []) @@ -460,7 +460,7 @@ process_add_constraint(Line, {Goals, State0}) -> case re:run(Line, ?ADD_VC, [{capture, all, list}]) of {match, [_All, _InstNumber, Pkg, Vsn, Dep, _Ignore, DepVsn]} -> {Goals, - rcl_depsolver:add_package_version(State0, Pkg, Vsn, [{Dep, DepVsn}])}; + rlx_depsolver:add_package_version(State0, Pkg, Vsn, [{Dep, DepVsn}])}; _ -> {Goals, State0} end. diff --git a/test/rlx_depsolver_tests.erl b/test/rlx_depsolver_tests.erl index eae31a4..7cbe831 100644 --- a/test/rlx_depsolver_tests.erl +++ b/test/rlx_depsolver_tests.erl @@ -20,7 +20,7 @@ %% %% @author Eric Merritt %%------------------------------------------------------------------- --module(rcl_depsolver_tests). +-module(rlx_depsolver_tests). -include_lib("eunit/include/eunit.hrl"). @@ -29,7 +29,7 @@ %%============================================================================ first_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2+build.33"}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2+build.33"}, {app3, "0.2", '>='}]}, {"0.2", []}, {"0.3", []}]}, @@ -41,7 +41,7 @@ first_test() -> {"0.3", []}]}]), - case rcl_depsolver:solve(Dom0, [{app1, "0.1"}]) of + case rlx_depsolver:solve(Dom0, [{app1, "0.1"}]) of {ok,[{app3,{{0,3},{[],[]}}}, {app2,{{0,2},{[],[<<"build">>,33]}}}, {app1,{{0,1},{[],[]}}}]} -> @@ -52,7 +52,7 @@ first_test() -> second_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.1", '>='}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.1", '>='}, {app4, "0.2"}, {app3, "0.2", '>='}]}, {"0.2", []}, @@ -68,7 +68,7 @@ second_test() -> {app3, "0.3"}]}, {"0.3", []}]}]), - X = rcl_depsolver:solve(Dom0, [{app1, "0.1"}, + X = rlx_depsolver:solve(Dom0, [{app1, "0.1"}, {app2, "0.3"}]), ?assertMatch({ok, [{app3,{{0,3},{[],[]}}}, @@ -86,7 +86,7 @@ third_test() -> Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, @@ -112,7 +112,7 @@ third_test() -> {app4,{{6,0,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), + rlx_depsolver:solve(Dom0, [{app1, "3.0"}])), ?assertMatch({ok, [{app5,{{6,0,0},{[],[]}}}, @@ -120,10 +120,10 @@ third_test() -> {app4,{{6,0,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [app1])). + rlx_depsolver:solve(Dom0, [app1])). fail_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, {app3, "0.2", gte}]}, {"0.2", []}, @@ -135,9 +135,9 @@ fail_test() -> {"0.2", []}, {"0.3", []}]}]), - Ret = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), + Ret = rlx_depsolver:solve(Dom0, [{app1, "0.1"}]), %% We do this to make sure all errors can be formated. - _ = rcl_depsolver:format_error(Ret), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[{app1,{{0,1},{[],[]}}}], [{app1,{{0,1},{[],[]}}},[[{app2,{{0,2},{[],[]}}}]]]}], @@ -154,7 +154,7 @@ conflicting_passing_test() -> Pkg2Deps = [{app4, "3.0.0", gte}], Pkg3Deps = [{app5, "2.0.0", '>='}], - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, @@ -181,23 +181,23 @@ conflicting_passing_test() -> {app4,{{5,0,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])), + rlx_depsolver:solve(Dom0, [{app1, "3.0"}])), ?assertMatch({ok, [{app5,{{2,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app4,{{5,0,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [app1, app2, app5])). + rlx_depsolver:solve(Dom0, [app1, app2, app5])). circular_dependencies_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", [app2]}]}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", [app2]}]}, {app2, [{"0.0.1", [app1]}]}]), ?assertMatch({ok, [{app1,{{0,1,0},{[],[]}}},{app2,{{0,0,1},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "0.1.0"}])). + rlx_depsolver:solve(Dom0, [{app1, "0.1.0"}])). conflicting_failing_test() -> Pkg1Deps = [app2, @@ -208,14 +208,14 @@ conflicting_failing_test() -> Pkg3Deps = [{app5, "6.0.0"}], - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"3.0", Pkg1Deps}]}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}]}, {app4, [{"5.0.0", [{app5, "2.0.0"}]}]}, {app5, [{"2.0.0", []}, {"6.0.0", []}]}]), - Ret = rcl_depsolver:solve(Dom0, [app1, app3]), - _ = rcl_depsolver:format_error(Ret), + Ret = rlx_depsolver:solve(Dom0, [app1, app3]), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[app1], [{app1,{{3,0},{[],[]}}}, @@ -237,7 +237,7 @@ pessimistic_major_minor_patch_test() -> Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, @@ -264,7 +264,7 @@ pessimistic_major_minor_patch_test() -> {app4,{{6,0,0},{[],[]}}}, {app2,{{2,1,5},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). + rlx_depsolver:solve(Dom0, [{app1, "3.0"}])). pessimistic_major_minor_test() -> @@ -275,7 +275,7 @@ pessimistic_major_minor_test() -> Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, @@ -302,7 +302,7 @@ pessimistic_major_minor_test() -> {app4,{{6,0,0},{[],[]}}}, {app2,{{2,2},{[],[]}}}, {app1,{{3,0},{[],[]}}}]}, - rcl_depsolver:solve(Dom0, [{app1, "3.0"}])). + rlx_depsolver:solve(Dom0, [{app1, "3.0"}])). filter_versions_test() -> @@ -347,18 +347,18 @@ filter_versions_test() -> {app4,"6.0.0"}, {app5,"2.0.0"}, {app5,"6.0.0"}]}, - rcl_depsolver:filter_packages(Packages, Cons)), + rlx_depsolver:filter_packages(Packages, Cons)), - Ret = rcl_depsolver:filter_packages(Packages, + Ret = rlx_depsolver:filter_packages(Packages, [{"foo", "1.0.0", '~~~~'} | Cons]), - _ = rcl_depsolver:format_error(Ret), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, {invalid_constraints, [{<<"foo">>,{{1,0,0},{[],[]}},'~~~~'}]}}, Ret). -spec missing_test() -> ok. missing_test() -> - Dom0 = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, + Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, {app3, "0.2", '>='}, {app4, "0.2", '='}]}, {"0.2", [{app4, "0.2"}]}, @@ -369,12 +369,12 @@ missing_test() -> {app3, [{"0.1", []}, {"0.2", []}, {"0.3", []}]}]), - Ret1 = rcl_depsolver:solve(Dom0, [{app4, "0.1"}, {app3, "0.1"}]), - _ = rcl_depsolver:format_error(Ret1), + Ret1 = rlx_depsolver:solve(Dom0, [{app4, "0.1"}, {app3, "0.1"}]), + _ = rlx_depsolver:format_error(Ret1), ?assertMatch({error,{unreachable_package,app4}}, Ret1), - Ret2 = rcl_depsolver:solve(Dom0, [{app1, "0.1"}]), - _ = rcl_depsolver:format_error(Ret2), + Ret2 = rlx_depsolver:solve(Dom0, [{app1, "0.1"}]), + _ = rlx_depsolver:format_error(Ret2), ?assertMatch({error,{unreachable_package,app4}}, Ret2). @@ -383,11 +383,11 @@ binary_test() -> World = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, [{<<"foo">>, <<"3.0.0">>, gt}]}]}], - Ret = rcl_depsolver:solve(rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + Ret = rlx_depsolver:solve(rlx_depsolver:add_packages(rlx_depsolver:new_graph(), World), [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, @@ -406,9 +406,9 @@ binary_test() -> %% doesnt_exist_test() -> Constraints = [{<<"foo">>,[{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}], - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), + World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), Constraints), + Ret = rlx_depsolver:solve(World, [<<"foo">>]), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error,{unreachable_package,<<"bar">>}}, Ret). %% @@ -426,9 +426,9 @@ not_new_enough_test() -> Constraints = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, []}]}], - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), Constraints), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), + World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), Constraints), + Ret = rlx_depsolver:solve(World, [<<"foo">>]), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, @@ -445,11 +445,11 @@ not_new_enough_test() -> %% "most_constrained_cookbooks:["bar = 2.0.0 -> [(foo > 3.0.0)]"] %% impossible_dependency_test() -> - World = rcl_depsolver:add_packages(rcl_depsolver:new_graph(), + World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{<<"foo">>, [{<<"1.2.3">>,[{ <<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, [{ <<"foo">>, <<"3.0.0">>, gt}]}]}]), - Ret = rcl_depsolver:solve(World, [<<"foo">>]), - _ = rcl_depsolver:format_error(Ret), + Ret = rlx_depsolver:solve(World, [<<"foo">>]), + _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, @@ -460,30 +460,30 @@ impossible_dependency_test() -> %% format_test_() -> [{"format constraint", - [equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(<<"foo">>)), - equal_bin_string(<<"foo">>, rcl_depsolver:format_constraint(foo)), - equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}})), - equal_bin_string(<<"(foo = 1.2.0)">>, rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '='})), + [equal_bin_string(<<"foo">>, rlx_depsolver:format_constraint(<<"foo">>)), + equal_bin_string(<<"foo">>, rlx_depsolver:format_constraint(foo)), + equal_bin_string(<<"(foo = 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}})), + equal_bin_string(<<"(foo = 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '='})), equal_bin_string(<<"(foo > 1.2.0)">>, - rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '>'})), + rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '>'})), equal_bin_string(<<"(foo > 1.2.0)">>, - rcl_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, gt})), + rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, gt})), equal_bin_string(<<"(foo between 1.2.0 and 1.3.0)">>, - rcl_depsolver:format_constraint({<<"foo">>,{{1,2,0}, {[], []}}, + rlx_depsolver:format_constraint({<<"foo">>,{{1,2,0}, {[], []}}, {{1,3,0}, {[], []}}, between})), equal_bin_string(<<"(foo > 1.2.0-alpha.1+build.36)">>, - rcl_depsolver:format_constraint({<<"foo">>, + rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {["alpha", 1], ["build", 36]}}, gt})) ] }, {"format roots", [equal_bin_string(<<"(bar = 1.2.0)">>, - rcl_depsolver:format_roots([ [{<<"bar">>, {{1,2,0},{[],[]}}}] ])), + rlx_depsolver:format_roots([ [{<<"bar">>, {{1,2,0},{[],[]}}}] ])), equal_bin_string(<<"(bar = 1.2.0), foo">>, - rcl_depsolver:format_roots([[<<"foo">>, + rlx_depsolver:format_roots([[<<"foo">>, {<<"bar">>, {{1,2,0},{[],[]}}}]])), equal_bin_string(<<"(bar = 1.2.0), foo">>, - rcl_depsolver:format_roots([[<<"foo">>], [{<<"bar">>, {{1,2,0},{[],[]}}}]])) + rlx_depsolver:format_roots([[<<"foo">>], [{<<"bar">>, {{1,2,0},{[],[]}}}]])) ] } ]. diff --git a/test/rlx_discover_SUITE.erl b/test/rlx_discover_SUITE.erl index e3a2861..d225ee6 100644 --- a/test/rlx_discover_SUITE.erl +++ b/test/rlx_discover_SUITE.erl @@ -17,7 +17,7 @@ %%%------------------------------------------------------------------- %%% @author Eric Merrit %%% @copyright (C) 2012, Eric Merrit --module(rclt_discover_SUITE). +-module(rlx_discover_SUITE). -export([suite/0, init_per_suite/1, @@ -44,9 +44,9 @@ init_per_testcase(_, Config) -> DataDir = proplists:get_value(data_dir, Config), LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), LibDir2 = filename:join([DataDir, create_random_name("lib_dir2_")]), - ok = rcl_util:mkdir_p(LibDir1), - ok = rcl_util:mkdir_p(LibDir2), - State = rcl_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), + ok = rlx_util:mkdir_p(LibDir1), + ok = rlx_util:mkdir_p(LibDir2), + State = rlx_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), [{lib1, LibDir1}, {lib2, LibDir2}, {state, State} | Config]. @@ -72,20 +72,20 @@ normal_case(Config) -> || App <- [{create_random_name("lib_app2_"), create_random_vsn()} || _ <- lists:seq(1, 100)]], - State0 = rcl_state:put(proplists:get_value(state, Config), + State0 = rlx_state:put(proplists:get_value(state, Config), disable_default_libs, true), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), - {ok, State2} = rcl_provider:do(DiscoverProvider, State1), + {DiscoverProvider, {ok, State1}} = rlx_provider:new(rlx_prv_discover, State0), + {ok, State2} = rlx_provider:do(DiscoverProvider, State1), lists:foreach(fun(App) -> - ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + ?assertMatch(true, lists:member(App, rlx_state:available_apps(State2))) end, Apps1), lists:foreach(fun(App) -> - ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + ?assertMatch(true, lists:member(App, rlx_state:available_apps(State2))) end, Apps2), Length = erlang:length(Apps2) + erlang:length(Apps2), - ?assertMatch(Length, erlang:length(rcl_state:available_apps(State2))). + ?assertMatch(Length, erlang:length(rlx_state:available_apps(State2))). no_beam_case(Config) -> %% We silently ignore apps with no beams @@ -110,10 +110,10 @@ no_beam_case(Config) -> AppDir = filename:join([LibDir2, BadName]), write_app_file(AppDir, BadName, BadVsn), State0 = proplists:get_value(state, Config), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + {DiscoverProvider, {ok, State1}} = rlx_provider:new(rlx_prv_discover, State0), EbinDir = filename:join([LibDir2, BadName, <<"ebin">>]), ?assertMatch({error, {_, [{no_beam_files, EbinDir}]}}, - rcl_provider:do(DiscoverProvider, State1)). + rlx_provider:do(DiscoverProvider, State1)). bad_ebin_case(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -140,9 +140,9 @@ bad_ebin_case(Config) -> ok = ec_file:write_term(Filename, get_bad_app_metadata(BadName, BadVsn)), write_beam_file(AppDir, BadName), State0 = proplists:get_value(state, Config), - {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + {DiscoverProvider, {ok, State1}} = rlx_provider:new(rlx_prv_discover, State0), ?assertMatch({error, {_, [{invalid_app_file, Filename}]}}, - rcl_provider:do(DiscoverProvider, State1)). + rlx_provider:do(DiscoverProvider, State1)). %%%=================================================================== @@ -152,7 +152,7 @@ create_app(Dir, Name, Vsn) -> AppDir = filename:join([Dir, Name]), write_app_file(AppDir, Name, Vsn), write_beam_file(AppDir, Name), - {ok, App} = rcl_app_info:new(erlang:list_to_atom(Name), Vsn, + {ok, App} = rlx_app_info:new(erlang:list_to_atom(Name), Vsn, erlang:iolist_to_binary(AppDir), [kernel, stdlib], []), App. diff --git a/test/rlx_goal_tests.erl b/test/rlx_goal_tests.erl index 20fb5e5..9a99bcb 100644 --- a/test/rlx_goal_tests.erl +++ b/test/rlx_goal_tests.erl @@ -18,46 +18,46 @@ %%% @author Eric Merritt %%% @copyright (C) 2012 Erlware, LLC. %%% @doc test for target spec parsing --module(rclt_goal). +-module(rlx_goal_tests). -include_lib("eunit/include/eunit.hrl"). parse_test() -> ?assertMatch({ok, getopt}, - rcl_goal:parse("getopt")), + rlx_goal:parse("getopt")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt=0.5.1")), + rlx_goal:parse("getopt=0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt:0.5.1")), + rlx_goal:parse("getopt:0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, '='}}, - rcl_goal:parse("getopt-0.5.1")), + rlx_goal:parse("getopt-0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, - rcl_goal:parse("getopt >= 0.5.1")), + rlx_goal:parse("getopt >= 0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gte}}, - rcl_goal:parse("getopt:gte:0.5.1")), + rlx_goal:parse("getopt:gte:0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, - rcl_goal:parse("getopt>0.5.1")), + rlx_goal:parse("getopt>0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, gt}}, - rcl_goal:parse("getopt:gt:0.5.1")), + rlx_goal:parse("getopt:gt:0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, - rcl_goal:parse("getopt<= 0.5.1")), + rlx_goal:parse("getopt<= 0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lte}}, - rcl_goal:parse("getopt:lte:0.5.1")), + rlx_goal:parse("getopt:lte:0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, lt}}, - rcl_goal:parse("getopt<0.5.1")), + rlx_goal:parse("getopt<0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, - rcl_goal:parse("getopt ~>0.5.1")), + rlx_goal:parse("getopt ~>0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, pes}}, - rcl_goal:parse("getopt: pes:0.5.1")), + rlx_goal:parse("getopt: pes:0.5.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, - rcl_goal:parse("getopt:btwn:0.5.1,0.6.1")), + rlx_goal:parse("getopt:btwn:0.5.1,0.6.1")), ?assertMatch({ok, {getopt, {{0,5,1},{[],[]}}, {{0,6,1},{[],[]}}, between}}, - rcl_goal:parse("getopt:between :0.5.1,0.6.1")). + rlx_goal:parse("getopt:between :0.5.1,0.6.1")). fail_test() -> ?assertMatch({fail,_}, - rcl_goal:parse("got:")), + rlx_goal:parse("got:")), ?assertMatch({fail,_}, - rcl_goal:parse("between:btwn:0.5")), + rlx_goal:parse("between:btwn:0.5")), ?assertMatch({fail,_}, - rcl_goal:parse("between:btwn:0.5,")). + rlx_goal:parse("between:btwn:0.5,")). diff --git a/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl index 90d99c9..ea4b20d 100644 --- a/test/rlx_release_SUITE.erl +++ b/test/rlx_release_SUITE.erl @@ -17,7 +17,7 @@ %%%------------------------------------------------------------------- %%% @author Eric Merrit %%% @copyright (C) 2012, Eric Merrit --module(rclt_release_SUITE). +-module(rlx_release_SUITE). -export([suite/0, init_per_suite/1, @@ -55,8 +55,8 @@ end_per_suite(_Config) -> init_per_testcase(_, Config) -> DataDir = proplists:get_value(data_dir, Config), LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), - ok = rcl_util:mkdir_p(LibDir1), - State = rcl_state:new([{lib_dirs, [LibDir1]}], release), + ok = rlx_util:mkdir_p(LibDir1), + State = rlx_state:new([{lib_dirs, [LibDir1]}], release), [{lib1, LibDir1}, {state, State} | Config]. @@ -84,17 +84,17 @@ make_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + create_random_name("relx-output")]), + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -119,15 +119,15 @@ make_invalid_config_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), ok = ec_file:write(ConfigFile, "{release, {foo, \"0.0.1\"}, [goal_app_1, goal_app_2,]}"), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {error, {rcl_prv_config, - {consult, _, _}}} = relcool:do(undefined, undefined, [], [LibDir1], 2, + create_random_name("relx-output")]), + {error, {rlx_prv_config, + {consult, _, _}}} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile). make_scriptless_release(Config) -> @@ -146,22 +146,22 @@ make_scriptless_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{generate_start_script, false}, {release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + create_random_name("relx-output")]), + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo"]))), ?assert(not ec_file:exists(filename:join([OutputDir, "bin", "foo-0.0.1"]))), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -195,20 +195,20 @@ make_overridden_release(Config) -> create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, erlang:list_to_atom(OverrideApp), goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), + create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), - {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -244,20 +244,20 @@ make_skip_app_release(Config) -> create_empty_app(SkipAppDir1, SkipAppApp, SkipAppVsn, [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}, {skip_apps, [erlang:list_to_atom(SkipAppApp)]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), + create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), - {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -285,20 +285,20 @@ make_implicit_config_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), + create_random_name("relx-output")]), ok = file:set_cwd(FooRoot), {ok, FooRoot} = file:get_cwd(), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, undefined), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), ?assert(ec_file:exists(OutputDir)), - AppSpecs = rcl_release:applications(Release), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -332,26 +332,26 @@ make_rerun_overridden_release(Config) -> create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, erlang:list_to_atom(OverrideApp), goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), + create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), - {ok, _} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + {ok, _} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], ConfigFile), %% Now we run it again to see if it fails. - {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2, + {ok, State} = relx:do(Cwd,undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -380,7 +380,7 @@ overlay_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), OverlayVars = filename:join([LibDir1, "vars.config"]), Template = filename:join([LibDir1, "test_template"]), write_config(ConfigFile, @@ -408,13 +408,13 @@ overlay_release(Config) -> ok = file:write_file_info(TemplateFile, FileInfo#file_info{mode=8#00777}), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), + create_random_name("relx-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -514,14 +514,14 @@ make_goalless_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, []}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - ?assertMatch({error,{rcl_prv_release,no_goals_specified}}, - relcool:do(undefined, undefined, [], [LibDir1], 2, + create_random_name("relx-output")]), + ?assertMatch({error,{rlx_prv_release,no_goals_specified}}, + relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile)). make_depfree_release(Config) -> @@ -540,16 +540,16 @@ make_depfree_release(Config) -> create_app(LibDir1, "non_goal_1", "0.0.1", [kernel,stdlib], []), create_app(LibDir1, "non_goal_2", "0.0.1", [kernel,stdlib], []), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, + create_random_name("relx-output")]), + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 2, OutputDir, ConfigFile), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)). @@ -576,7 +576,7 @@ make_relup_release(Config) -> write_appup_file(GA1, "0.0.2"), write_appup_file(GA2, "0.0.2"), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [sasl, @@ -591,12 +591,12 @@ make_relup_release(Config) -> {goal_app_1, "0.0.3"}, {goal_app_2, "0.0.3"}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, + create_random_name("relx-output")]), + {ok, _} = relx:do(foo, "0.0.1", [], [LibDir1], 2, OutputDir, ConfigFile), - {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, + {ok, _} = relx:do(foo, "0.0.2", [], [LibDir1], 2, OutputDir, ConfigFile), - {ok, State} = relcool:do([{relname, foo}, + {ok, State} = relx:do([{relname, foo}, {relvsn, "0.0.3"}, {goals, []}, {lib_dirs, [LibDir1]}, @@ -608,24 +608,24 @@ make_relup_release(Config) -> ?assertMatch([{foo, "0.0.1"}, {foo, "0.0.2"}, {foo, "0.0.3"}], - lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), - Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), - ?assert(rcl_release:realized(Release)), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, - rcl_state:realized_releases(State)))), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, - rcl_state:realized_releases(State)))), + lists:sort(ec_dictionary:keys(rlx_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rlx_state:realized_releases(State)), + ?assert(rlx_release:realized(Release)), + ?assert(not rlx_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rlx_state:realized_releases(State)))), + ?assert(not rlx_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rlx_state:realized_releases(State)))), ?assertMatch({ok, [{"0.0.3", [{"0.0.2",[],[point_of_no_return]}], [{"0.0.2",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), - filename:basename(rcl_release:relfile(Release), ".rel") ++ + file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), + filename:basename(rlx_release:relfile(Release), ".rel") ++ ".relup"))), - ?assertMatch(foo, rcl_release:name(Release)), - ?assertMatch("0.0.3", rcl_release:vsn(Release)), - AppSpecs = rcl_release:applications(Release), + ?assertMatch(foo, rlx_release:name(Release)), + ?assertMatch("0.0.3", rlx_release:vsn(Release)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -658,7 +658,7 @@ make_relup_release2(Config) -> write_appup_file(GA1, "0.0.1"), write_appup_file(GA2, "0.0.1"), - ConfigFile = filename:join([LibDir1, "relcool.config"]), + ConfigFile = filename:join([LibDir1, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [sasl, @@ -673,12 +673,12 @@ make_relup_release2(Config) -> {goal_app_1, "0.0.3"}, {goal_app_2, "0.0.3"}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relcool-output")]), - {ok, _} = relcool:do(foo, "0.0.1", [], [LibDir1], 2, + create_random_name("relx-output")]), + {ok, _} = relx:do(foo, "0.0.1", [], [LibDir1], 2, OutputDir, ConfigFile), - {ok, _} = relcool:do(foo, "0.0.2", [], [LibDir1], 2, + {ok, _} = relx:do(foo, "0.0.2", [], [LibDir1], 2, OutputDir, ConfigFile), - {ok, State} = relcool:do([{relname, foo}, + {ok, State} = relx:do([{relname, foo}, {relvsn, "0.0.3"}, {upfrom, "0.0.1"}, {goals, []}, @@ -691,24 +691,24 @@ make_relup_release2(Config) -> ?assertMatch([{foo, "0.0.1"}, {foo, "0.0.2"}, {foo, "0.0.3"}], - lists:sort(ec_dictionary:keys(rcl_state:realized_releases(State)))), - Release = ec_dictionary:get({foo, "0.0.3"}, rcl_state:realized_releases(State)), - ?assert(rcl_release:realized(Release)), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, - rcl_state:realized_releases(State)))), - ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, - rcl_state:realized_releases(State)))), + lists:sort(ec_dictionary:keys(rlx_state:realized_releases(State)))), + Release = ec_dictionary:get({foo, "0.0.3"}, rlx_state:realized_releases(State)), + ?assert(rlx_release:realized(Release)), + ?assert(not rlx_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rlx_state:realized_releases(State)))), + ?assert(not rlx_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rlx_state:realized_releases(State)))), ?assertMatch({ok, [{"0.0.3", [{"0.0.1",[],[point_of_no_return]}], [{"0.0.1",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rcl_release:relfile(Release)), - filename:basename(rcl_release:relfile(Release), ".rel") ++ + file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), + filename:basename(rlx_release:relfile(Release), ".rel") ++ ".relup"))), - ?assertMatch(foo, rcl_release:name(Release)), - ?assertMatch("0.0.3", rcl_release:vsn(Release)), - AppSpecs = rcl_release:applications(Release), + ?assertMatch(foo, rlx_release:name(Release)), + ?assertMatch("0.0.3", rlx_release:vsn(Release)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), @@ -721,22 +721,22 @@ make_relup_release2(Config) -> make_one_app_top_level_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), {ok, AppInfo} = create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel], []), - AppDir = rcl_app_info:dir(AppInfo), - ConfigFile = filename:join([AppDir, "relcool.config"]), + AppDir = rlx_app_info:dir(AppInfo), + ConfigFile = filename:join([AppDir, "relx.config"]), write_config(ConfigFile, [{release, {foo, "0.0.1"}, [{goal_app_1, "0.0.1"}]}]), OutputDir = filename:join([AppDir, - create_random_name("relcool-output")]), + create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), ok = file:set_cwd(AppDir), - {ok, State} = relcool:do(undefined, undefined, [], [], 2, + {ok, State} = relx:do(undefined, undefined, [], [], 2, OutputDir, ConfigFile), ok = file:set_cwd(Cwd), - [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:realized_releases(State)), - AppSpecs = rcl_release:applications(Release), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), ?assert(lists:keymember(stdlib, 1, AppSpecs)), ?assert(lists:keymember(kernel, 1, AppSpecs)), ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)). @@ -750,13 +750,13 @@ create_app(Dir, Name, Vsn, Deps, LibDeps) -> AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), write_app_file(AppDir, Name, Vsn, Deps, LibDeps), write_beam_file(AppDir, Name), - rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, Deps, []). create_empty_app(Dir, Name, Vsn, Deps, LibDeps) -> AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), write_app_file(AppDir, Name, Vsn, Deps, LibDeps), - rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, Deps, []). write_beam_file(Dir, Name) -> @@ -765,9 +765,9 @@ write_beam_file(Dir, Name) -> ok = ec_file:write_term(Beam, testing_purposes_only). write_appup_file(AppInfo, DownVsn) -> - Dir = rcl_app_info:dir(AppInfo), - Name = rcl_util:to_string(rcl_app_info:name(AppInfo)), - Vsn = rcl_app_info:vsn_as_string(AppInfo), + Dir = rlx_app_info:dir(AppInfo), + Name = rlx_util:to_string(rlx_app_info:name(AppInfo)), + Vsn = rlx_app_info:vsn_as_string(AppInfo), Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), ok = filelib:ensure_dir(Filename), ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). -- cgit v1.2.3