aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--README.md3
-rw-r--r--rebar.config2
-rw-r--r--src/rcl_cmd_args.erl33
-rw-r--r--src/rcl_log.erl11
-rw-r--r--src/rcl_prv_assembler.erl17
-rw-r--r--src/rcl_prv_config.erl14
-rw-r--r--src/rcl_prv_discover.erl29
-rw-r--r--src/rcl_prv_overlay.erl202
-rw-r--r--src/rcl_prv_release.erl17
-rw-r--r--src/rcl_state.erl52
-rw-r--r--src/relcool.erl61
-rw-r--r--test/rclt_command_SUITE.erl3
-rw-r--r--test/rclt_discover_SUITE.erl11
-rw-r--r--test/rclt_release_SUITE.erl167
15 files changed, 455 insertions, 173 deletions
diff --git a/Makefile b/Makefile
index 2e43f9d..1cf2e23 100644
--- a/Makefile
+++ b/Makefile
@@ -61,7 +61,11 @@ eunit: compile clean-common-test-data
$(REBAR) skip_deps=true eunit
ct: compile clean-common-test-data
- $(REBAR) skip_deps=true ct
+ ct_run -pa $(CURDIR)/ebin \
+ -pa $(CURDIR)/deps/*/ebin \
+ -logdir $(CURDIR)/logs \
+ -dir $(CURDIR)/test/ \
+ -suite rclt_command_SUITE rclt_discover_SUITE -suite rclt_release_SUITE
test: compile eunit ct
diff --git a/README.md b/README.md
index 30e5c82..99fef81 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,9 @@ additional specification information for releases.
# OPTIONS
+-r *STRING*, \--root *STRING*
+: Specify the root directory for the project (if different from cwd)
+
-n *STRING*, \--relname *STRING*
: Specify the name for the release that will be generated
diff --git a/rebar.config b/rebar.config
index 3561991..8ffc212 100644
--- a/rebar.config
+++ b/rebar.config
@@ -30,6 +30,6 @@
%% Misc =======================================================================
{escript_incl_apps,
- [getopt, erlware_commons]}.
+ [getopt, erlware_commons, erlydtl]}.
{first_files, [rcl_provider]}.
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl
index dc176be..6340201 100644
--- a/src/rcl_cmd_args.erl
+++ b/src/rcl_cmd_args.erl
@@ -34,7 +34,8 @@
relcool:error().
args2state({error, Detail}) ->
?RCL_ERROR({opt_parse, Detail});
-args2state({ok, {Opts, Targets}}) ->
+args2state({ok, {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 create_log(Opts,
@@ -43,15 +44,19 @@ args2state({ok, {Opts, Targets}}) ->
Error = {error, _} ->
Error;
{ok, CommandLineConfig} ->
- case validate_configs(Targets) of
+ case validate_configs(Target) of
Error = {error, _} ->
Error;
{ok, Configs} ->
- {ok, {rcl_state:new(CommandLineConfig, Configs), Configs}}
+ {ok, rcl_state:new(CommandLineConfig, Configs)}
end
- end.
+ end;
+args2state({ok, {_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}) ->
@@ -155,9 +160,27 @@ create_lib_dirs(Opts, Acc) ->
Error = {error, _} ->
Error;
ok ->
- {ok, [{lib_dirs, [filename:absname(Dir) || Dir <- Dirs]} | 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().
+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),
+ {ok, [{disable_default_libs, Def} | Acc]}.
+
-spec check_lib_dirs([string()]) -> ok | relcool:error().
check_lib_dirs([]) ->
ok;
diff --git a/src/rcl_log.erl b/src/rcl_log.erl
index a5fb43a..a24b1c0 100644
--- a/src/rcl_log.erl
+++ b/src/rcl_log.erl
@@ -36,6 +36,7 @@
format/1]).
-export_type([int_log_level/0,
+ atom_log_level/0,
log_level/0,
log_fun/0,
t/0]).
@@ -46,10 +47,14 @@
%% 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 log_level() :: error | info | debug.
+-type atom_log_level() :: error | info | debug.
+
-opaque t() :: {?MODULE, int_log_level()}.
-type log_fun() :: fun(() -> iolist()).
@@ -58,7 +63,7 @@
%% API
%%============================================================================
%% @doc Create a new 'log level' for the system
--spec new(int_log_level() | log_level()) -> t().
+-spec new(log_level()) -> t().
new(LogLevel) when LogLevel >= 0, LogLevel =< 2 ->
{?MODULE, LogLevel};
new(AtomLogLevel)
@@ -152,7 +157,7 @@ log_level({?MODULE, DetailLogLevel}) ->
DetailLogLevel.
%% @doc get the current log level as an atom
--spec atom_log_level(t()) -> log_level().
+-spec atom_log_level(t()) -> atom_log_level().
atom_log_level({?MODULE, ?RCL_ERROR}) ->
error;
atom_log_level({?MODULE, ?RCL_INFO}) ->
diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl
index a721b12..61f6dad 100644
--- a/src/rcl_prv_assembler.erl
+++ b/src/rcl_prv_assembler.erl
@@ -210,11 +210,18 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
ErlOpts = rcl_state:get(State, erl_opts, ""),
StartFile = bin_file_contents(RelName, RelVsn,
rcl_release:erts(Release),
- ErlOpts),
- 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),
+ ErlOpts),
+ %% 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,
copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir).
%% @doc copy config/sys.config or generate one to releases/VSN/sys.config
diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl
index 7ea9a77..f87c467 100644
--- a/src/rcl_prv_config.erl
+++ b/src/rcl_prv_config.erl
@@ -29,11 +29,11 @@ init(State) ->
%% populating the state as a result.
-spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error().
do(State) ->
- case rcl_state:config_files(State) of
+ case rcl_state:config_file(State) of
[] ->
search_for_dominating_config(State);
- ConfigFiles ->
- lists:foldl(fun load_config/2, {ok, State}, ConfigFiles)
+ ConfigFile ->
+ load_config(ConfigFile, State)
end.
-spec format_error(Reason::term()) -> iolist().
@@ -63,7 +63,7 @@ search_for_dominating_config(State0) ->
%% 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, {ok, rcl_state:config_files(State1, [Config])});
+ load_config(Config, rcl_state:config_file(State1, Config));
no_config ->
{ok, State0}
end.
@@ -88,11 +88,9 @@ parent_dir([H | T], Acc) ->
parent_dir(T, [H | Acc]).
--spec load_config(file:filename(), {ok, rcl_state:t()} | relcool:error()) ->
+-spec load_config(file:filename(), rcl_state:t()) ->
{ok, rcl_state:t()} | relcool:error().
-load_config(_, Err = {error, _}) ->
- Err;
-load_config(ConfigFile, {ok, State}) ->
+load_config(ConfigFile, State) ->
{ok, CurrentCwd} = file:get_cwd(),
ok = file:set_cwd(filename:dirname(ConfigFile)),
Result = case file:consult(ConfigFile) of
diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl
index 0a12d24..4e7ce72 100644
--- a/src/rcl_prv_discover.erl
+++ b/src/rcl_prv_discover.erl
@@ -112,29 +112,20 @@ setup_overrides(State, AppMetas0) ->
get_lib_dirs(State) ->
LibDirs0 = rcl_state:lib_dirs(State),
- add_rebar_deps_dir(State, LibDirs0).
-
--spec add_rebar_deps_dir(rcl_state:t(), [file:name()]) -> [file:name()].
-add_rebar_deps_dir(State, LibDirs) ->
- ExcludeRebar = rcl_state:get(State, discover_exclude_rebar, false),
- case ExcludeRebar of
+ case rcl_state:get(State, disable_default_libs, false) of
true ->
- add_system_lib_dir(State, LibDirs);
+ LibDirs0;
false ->
- %% 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.
- {ok, Cwd} = file:get_cwd(),
-
- RebarConfig = filename:join([Cwd, "rebar.config"]),
- DepsDir = filename:join([Cwd, "deps"]),
- case filelib:is_regular(RebarConfig) andalso filelib:is_dir(DepsDir) of
- true ->
- add_system_lib_dir(State, [filename:absname(Cwd) | LibDirs]);
- false ->
- add_system_lib_dir(State, LibDirs)
- end
+ add_current_dir(State, LibDirs0)
end.
+-spec add_current_dir(rcl_state:t(), [file:name()]) -> [file:name()].
+add_current_dir(State, LibDirs) ->
+ %% 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.
+ Root = rcl_state:root_dir(State),
+ add_system_lib_dir(State, [filename:absname(Root) | LibDirs]).
+
-spec add_system_lib_dir(rcl_state:t(), [file:name()]) -> [file:name()].
add_system_lib_dir(State, LibDirs) ->
ExcludeSystem = rcl_state:get(State, discover_exclude_system, false),
diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl
index af914ee..94b5f3d 100644
--- a/src/rcl_prv_overlay.erl
+++ b/src/rcl_prv_overlay.erl
@@ -28,6 +28,8 @@
do/1,
format_error/1]).
+-define(DIRECTORY_RE, ".*(\/|\\\\)$").
+
-include_lib("relcool/include/relcool.hrl").
%%============================================================================
@@ -110,13 +112,55 @@ get_overlay_vars_from_file(State, OverlayVars) ->
-spec read_overlay_vars(rcl_state:t(), proplists:proplist(), file:name()) ->
{ok, rcl_state:t()} | relcool:error().
read_overlay_vars(State, OverlayVars, FileName) ->
- case file:consult(FileName) of
+ RelativeRoot = get_relative_root(State),
+ RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)),
+ case file:consult(RelativePath) of
{ok, Terms} ->
- do_overlay(State, OverlayVars ++ 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)},
@@ -154,7 +198,7 @@ generate_state_vars(State) ->
{goals, [rcl_depsolver:format_constraint(Constraint) ||
Constraint <- rcl_state:goals(State)]},
{lib_dirs, rcl_state:lib_dirs(State)},
- {config_files, rcl_state:config_files(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)},
@@ -181,13 +225,10 @@ do_overlay(State, OverlayVars) ->
{ok, State};
Overlays ->
handle_errors(State,
- ec_plists:map(fun(Overlay) ->
- io:format("--->Doing ~p~n", [Overlay]),
- Res = do_individual_overlay(State, OverlayVars,
- Overlay),
- io:format("-->Done ~p~n", [Overlay]),
- Res
- end, Overlays))
+ lists:map(fun(Overlay) ->
+ do_individual_overlay(State, OverlayVars,
+ Overlay)
+ end, Overlays))
end.
-spec handle_errors(rcl_state:t(), [ok | relcool:error()]) ->
@@ -206,16 +247,13 @@ handle_errors(State, Result) ->
{ok, rcl_state:t()} | relcool:error().
do_individual_overlay(State, OverlayVars, {mkdir, Dir}) ->
ModuleName = make_template_name("rcl_mkdir_template", Dir),
- io:format("compiling to ~p ~n", [ModuleName]),
case erlydtl:compile(erlang:iolist_to_binary(Dir), ModuleName) of
{ok, ModuleName} ->
- io:format("compiled ~n"),
case render(ModuleName, OverlayVars) of
{ok, IoList} ->
- io:format("rendered ~n"),
- Absolute = filename:absname(filename:join(rcl_state:root_dir(State),
- erlang:iolist_to_binary(IoList))),
- io:format("got ~p ~n", [Absolute]),
+ 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});
@@ -231,72 +269,124 @@ do_individual_overlay(State, OverlayVars, {mkdir, Dir}) ->
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(State, OverlayVars, From, FromTemplateName,
+ file_render_do(OverlayVars, From, FromTemplateName,
fun(FromFile) ->
- file_render_do(State, OverlayVars, To, ToTemplateName,
+ file_render_do(OverlayVars, To, ToTemplateName,
fun(ToFile) ->
- filelib:ensure_dir(ToFile),
- case ec_file:copy(FromFile, ToFile) of
- ok ->
- ok;
- {error, Err} ->
- ?RCL_ERROR({copy_failed,
- FromFile,
- ToFile, Err})
- end
+ 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(State, OverlayVars, From, FromTemplateName,
+ file_render_do(OverlayVars, From, FromTemplateName,
fun(FromFile) ->
- file_render_do(State, OverlayVars, To, ToTemplateName,
+ file_render_do(OverlayVars, To, ToTemplateName,
fun(ToFile) ->
- render_template(OverlayVars,
- erlang:binary_to_list(FromFile),
- 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 render_template(proplists:proplist(), iolist(), file:name()) ->
+-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, FromFile, ToFile) ->
- TemplateName = make_template_name("rcl_template_renderer", FromFile),
- case erlydtl:compile(FromFile, TemplateName) of
- Good when Good =:= ok; ok =:= {ok, TemplateName} ->
+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} ->
- io:format("Rendering ~p~n", [IoData]),
- case filelib:ensure_dir(ToFile) of
+ {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 ->
- case file:write_file(ToFile, IoData) of
- ok ->
- ok;
- {error, Reason} ->
- ?RCL_ERROR({unable_to_write, ToFile, Reason})
- end;
+ {ok, FileInfo} = file:read_file_info(FromFile),
+ ok = file:write_file_info(ToFile, FileInfo),
+ ok;
{error, Reason} ->
- ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason})
+ ?RCL_ERROR({unable_to_write, ToFile, Reason})
end;
{error, Reason} ->
- ?RCL_ERROR({unable_to_render_template, FromFile, Reason})
+ ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason})
end;
- {error, Reason} ->
- ?RCL_ERROR({unable_to_compile_template, FromFile, Reason})
+ Error ->
+ Error
end.
--spec file_render_do(rcl_state:t(), proplists:proplist(), iolist(), module(),
+-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(State, OverlayVars, Data, TemplateName, NextAction) ->
+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} ->
- Absolute = filename:absname(filename:join(rcl_state:root_dir(State),
- erlang:iolist_to_binary(IoList))),
- NextAction(Absolute);
+ NextAction(IoList);
{error, Error} ->
?RCL_ERROR({render_failed, Data, Error})
end;
@@ -322,3 +412,7 @@ render(ModuleName, OverlayVars) ->
_: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
index 66f4e11..8a86e02 100644
--- a/src/rcl_prv_release.erl
+++ b/src/rcl_prv_release.erl
@@ -47,6 +47,8 @@ do(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}) ->
@@ -151,11 +153,16 @@ solve_release(State0, DepGraph, RelName, RelVsn) ->
try
Release = rcl_state:get_release(State0, RelName, RelVsn),
Goals = rcl_release:goals(Release),
- case rcl_depsolver:solve(DepGraph, Goals) of
- {ok, Pkgs} ->
- set_resolved(State0, Release, Pkgs);
- {error, Error} ->
- ?RCL_ERROR({failed_solve, Error})
+ 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 ->
diff --git a/src/rcl_state.erl b/src/rcl_state.erl
index eb70ecc..842b635 100644
--- a/src/rcl_state.erl
+++ b/src/rcl_state.erl
@@ -30,8 +30,8 @@
overrides/1,
overrides/2,
goals/1,
- config_files/1,
- config_files/2,
+ config_file/1,
+ config_file/2,
providers/1,
providers/2,
sys_config/1,
@@ -64,7 +64,7 @@
caller :: caller(),
output_dir :: file:name(),
lib_dirs=[] :: [file:name()],
- config_files=[] :: [file:filename()],
+ config_file=[] :: file:filename(),
goals=[] :: [rcl_depsolver:constraint()],
providers = [] :: [rcl_provider:t()],
available_apps = [] :: [rcl_app_info:t()],
@@ -93,13 +93,13 @@
%%============================================================================
%% @doc Create a new 'log level' for the system
-spec new(proplists:proplist(), [file:filename()] | file:filename()) -> t().
-new(PropList, Targets) when erlang:is_list(PropList) ->
+new(PropList, Target) when erlang:is_list(PropList) ->
{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=get_lib_dirs(proplists:get_value(lib_dirs, PropList, [])),
- config_files=process_config_files(Targets),
+ lib_dirs=proplists:get_value(lib_dirs, PropList, ""),
+ config_file=Target,
goals=proplists:get_value(goals, PropList, []),
providers = [],
releases=ec_dictionary:new(ec_dict),
@@ -108,7 +108,9 @@ new(PropList, Targets) when erlang:is_list(PropList) ->
root_dir = proplists:get_value(root_dir, PropList, Root),
default_release={proplists:get_value(relname, PropList, undefined),
proplists:get_value(relvsn, PropList, undefined)}},
- create_logic_providers(State0).
+ rcl_state:put(create_logic_providers(State0),
+ disable_default_libs,
+ proplists:get_value(disable_default_libs, PropList, false)).
%% @doc the application overrides for the system
-spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}].
@@ -137,13 +139,13 @@ lib_dirs(#state_t{lib_dirs=LibDir}) ->
goals(#state_t{goals=TS}) ->
TS.
--spec config_files(t()) -> [file:filename()].
-config_files(#state_t{config_files=ConfigFiles}) ->
+-spec config_file(t()) -> file:filename().
+config_file(#state_t{config_file=ConfigFiles}) ->
ConfigFiles.
--spec config_files(t(), [file:filename()]) -> t().
-config_files(State, ConfigFiles) ->
- State#state_t{config_files=ConfigFiles}.
+-spec config_file(t(), file:filename()) -> t().
+config_file(State, ConfigFiles) ->
+ State#state_t{config_file=ConfigFiles}.
-spec providers(t()) -> [rcl_provider:t()].
providers(#state_t{providers=Providers}) ->
@@ -243,15 +245,14 @@ format(Mod) ->
-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_files=ConfigFiles,
+ 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 files: \n",
- [[rcl_util:indent(Indent + 2), ConfigFile, ",\n"] || ConfigFile <- ConfigFiles],
+ rcl_util:indent(Indent + 1), "config file: ", 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",
@@ -265,15 +266,6 @@ format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs,
%%%===================================================================
%%% Internal Functions
%%%===================================================================
--spec get_lib_dirs([file:name()]) -> [file:name()].
-get_lib_dirs(CmdDirs) ->
- case os:getenv("ERL_LIBS") of
- false ->
- CmdDirs;
- EnvString ->
- [Lib || Lib <- re:split(EnvString, ":|;"),
- filelib:is_dir(Lib)] ++ CmdDirs
- end.
-spec create_logic_providers(t()) -> t().
create_logic_providers(State0) ->
@@ -285,18 +277,6 @@ create_logic_providers(State0) ->
State5#state_t{providers=[ConfigProvider, DiscoveryProvider,
ReleaseProvider, OverlayProvider, AssemblerProvider]}.
-
-%% @doc config files can come in as either a single file name or as a list of
-%% files. We what to support both where possible.
-process_config_files(File = [Char | _])
- when erlang:is_integer(Char) ->
- [File];
-process_config_files(Files = [File | _])
- when erlang:is_list(File) ->
- Files;
-process_config_files([]) ->
- [].
-
%%%===================================================================
%%% Test Functions
%%%===================================================================
diff --git a/src/relcool.erl b/src/relcool.erl
index c6747ca..974b19f 100644
--- a/src/relcool.erl
+++ b/src/relcool.erl
@@ -23,6 +23,7 @@
-export([main/1,
do/7,
do/8,
+ do/9,
format_error/1,
opt_spec_list/0]).
@@ -35,6 +36,7 @@
%%============================================================================
-type error() :: {error, {Module::module(), Reason::term()}}.
+-type goal() :: string() | binary() | rcl_depsolver:constraint().
%%============================================================================
%% API
@@ -43,7 +45,7 @@
main(Args) ->
OptSpecList = opt_spec_list(),
case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of
- {ok, {State, _Target}} ->
+ {ok, State} ->
run_relcool_process(rcl_state:caller(State, command_line));
Error={error, _} ->
report_error(rcl_state:caller(rcl_state:new([], []),
@@ -59,11 +61,32 @@ main(Args) ->
%% @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
-do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) ->
- do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Configs).
+-spec do(atom(), string(), [goal()], [file:name()], rcl_log:log_level(),
+ [file:name()], file:name()) ->
+ 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()) ->
+ 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
@@ -72,29 +95,39 @@ do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) ->
%% @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
-do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Configs) ->
+-spec do(file:name(), atom(), string(), [goal()], [file:name()],
+ rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name()) ->
+ 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)}],
- Configs),
+ Config),
run_relcool_process(rcl_state:caller(State, api)).
-spec opt_spec_list() -> [getopt:option_spec()].
opt_spec_list() ->
- [
- {relname, $n, "relname", string, "Specify the name for the release that will be generated"},
+ [{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, "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."},
- {lib_dir, $l, "lib-dir", string, "Additional dirs that should be searched for OTP Apps"},
- {log_level, $V, "verbose", {integer, 0}, "Verbosity level, maybe between 0 and 2"}
- ].
+ {goals, $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."},
+ {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"},
+ {root_dir, $r, "root", string, "The project root directory"}].
-spec format_error(Reason::term()) -> iolist().
format_error({invalid_return_value, Provider, Value}) ->
@@ -103,7 +136,6 @@ format_error({invalid_return_value, Provider, Value}) ->
format_error({error, {Module, Reason}}) ->
io_lib:format("~s~n", [Module:format_error(Reason)]).
-
%%============================================================================
%% internal api
%%============================================================================
@@ -170,7 +202,6 @@ run_provider(Provider, {ok, State0}) ->
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)),
diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl
index 1c6accf..bd99da0 100644
--- a/test/rclt_command_SUITE.erl
+++ b/test/rclt_command_SUITE.erl
@@ -58,8 +58,7 @@ 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, Target}} = rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine)),
- ?assertMatch([], Target),
+ {ok, State} = rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine)),
?assertMatch([Lib1, Lib2],
rcl_state:lib_dirs(State)),
?assertMatch(Outdir, rcl_state:output_dir(State)),
diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl
index 63d7841..6b61840 100644
--- a/test/rclt_discover_SUITE.erl
+++ b/test/rclt_discover_SUITE.erl
@@ -62,8 +62,8 @@ normal_case(Config) ->
end)(App)
||
App <-
- [{create_random_name("lib_app1_"), create_random_vsn()}
- || _ <- lists:seq(1, 100)]],
+ [{create_random_name("lib_app1_"), create_random_vsn()}
+ || _ <- lists:seq(1, 100)]],
LibDir2 = proplists:get_value(lib2, Config),
Apps2 = [(fun({Name, Vsn}) ->
@@ -71,10 +71,9 @@ normal_case(Config) ->
end)(App)
|| App <-
[{create_random_name("lib_app2_"), create_random_vsn()}
- || _ <- lists:seq(1, 100)]],
- State0 = rcl_state:put(rcl_state:put(proplists:get_value(state, Config),
- discover_exclude_rebar, true),
- discover_exclude_system, true),
+ || _ <- 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) ->
diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl
index f4f0ecc..9c9c70c 100644
--- a/test/rclt_release_SUITE.erl
+++ b/test/rclt_release_SUITE.erl
@@ -25,14 +25,19 @@
init_per_testcase/2,
all/0,
make_release/1,
+ make_scriptless_release/1,
make_overridden_release/1,
make_rerun_overridden_release/1,
make_implicit_config_release/1,
- overlay_release/1]).
+ overlay_release/1,
+ make_goalless_release/1,
+ make_depfree_release/1,
+ make_invalid_config_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}}].
@@ -52,8 +57,10 @@ init_per_testcase(_, Config) ->
{state, State} | Config].
all() ->
- [make_release, make_overridden_release, make_implicit_config_release,
- make_rerun_overridden_release, overlay_release].
+ [make_release, make_scriptless_release, make_overridden_release,
+ make_implicit_config_release, make_rerun_overridden_release,
+ overlay_release, make_goalless_release, make_depfree_release,
+ make_invalid_config_release].
make_release(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -90,6 +97,73 @@ make_release(Config) ->
?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: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),
@@ -123,7 +197,8 @@ make_overridden_release(Config) ->
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,
+ {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:releases(State)),
@@ -171,7 +246,6 @@ make_implicit_config_release(Config) ->
OutputDir, []),
[{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)),
?assert(ec_file:exists(OutputDir)),
-
AppSpecs = rcl_release:applications(Release),
?assert(lists:keymember(stdlib, 1, AppSpecs)),
?assert(lists:keymember(kernel, 1, AppSpecs)),
@@ -213,12 +287,13 @@ make_rerun_overridden_release(Config) ->
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,
+ {ok, Cwd} = file:get_cwd(),
+ {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2,
OutputDir, [{OverrideAppName, OverrideAppDir}],
[ConfigFile]),
%% Now we run it again to see if it failse.
- {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2,
+ {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2,
OutputDir, [{OverrideAppName, OverrideAppDir}],
[ConfigFile]),
@@ -260,6 +335,8 @@ overlay_release(Config) ->
{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"},
@@ -269,10 +346,13 @@ overlay_release(Config) ->
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")]),
@@ -292,13 +372,16 @@ overlay_release(Config) ->
?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
+ 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)),
@@ -341,7 +424,7 @@ overlay_release(Config) ->
?assertEqual([""],
proplists:get_value(goals, TemplateData)),
?assert(proplists:is_defined(lib_dirs, TemplateData)),
- ?assert(proplists:is_defined(config_files, TemplateData)),
+ ?assert(proplists:is_defined(config_file, TemplateData)),
?assertEqual([""],
proplists:get_value(goals, TemplateData)),
?assertEqual("undefined",
@@ -358,7 +441,64 @@ overlay_release(Config) ->
?assertEqual("bar",
proplists:get_value(yahoo2_foo, TemplateData)),
?assertEqual("foodir",
- proplists:get_value(foo_dir, TemplateData)).
+ 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:releases(State)),
+ AppSpecs = rcl_release:applications(Release),
+ ?assert(lists:keymember(stdlib, 1, AppSpecs)),
+ ?assert(lists:keymember(kernel, 1, AppSpecs)).
%%%===================================================================
%%% Helper Functions
@@ -405,7 +545,7 @@ write_config(Filename, Values) ->
test_template_contents() ->
"{erts_vsn, \"{{erts_vsn}}\"}.\n"
- "{release_erts_version, \"{{release_erts_version}}\"}.\n"
+ "{release_erts_version, \"{{release_erts_version}}\"}.\n"
"{release_name, {{release_name}}}.\n"
"{rel_vsn, \"{{release_version}}\"}.\n"
"{release_version, \"{{release_version}}\"}.\n"
@@ -427,7 +567,7 @@ test_template_contents() ->
"{overridden, [{{ overridden|join:\", \" }}]}.\n"
"{goals, [\"{{ goals|join:\", \" }}\"]}.\n"
"{lib_dirs, [\"{{ lib_dirs|join:\", \" }}\"]}.\n"
- "{config_files, [\"{{ config_files|join:\", \" }}\"]}.\n"
+ "{config_file, \"{{ config_file }}\"}.\n"
"{providers, [{{ providers|join:\", \" }}]}.\n"
"{sys_config, \"{{sys_config}}\"}.\n"
"{root_dir, \"{{root_dir}}\"}.\n"
@@ -436,4 +576,5 @@ test_template_contents() ->
"{default_release, \"{{default_release}}\"}.\n"
"{yahoo, \"{{yahoo}}\"}.\n"
"{yahoo2_foo, \"{{yahoo2.foo}}\"}.\n"
- "{foo_dir, \"{{foo_dir}}\"}.\n".
+ "{foo_dir, \"{{foo_dir}}\"}.\n"
+ "{yahoo3, \"{{yahoo3.bar}}\"}.\n".