diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | rebar.config | 2 | ||||
-rw-r--r-- | src/rcl_cmd_args.erl | 33 | ||||
-rw-r--r-- | src/rcl_log.erl | 11 | ||||
-rw-r--r-- | src/rcl_prv_assembler.erl | 17 | ||||
-rw-r--r-- | src/rcl_prv_config.erl | 14 | ||||
-rw-r--r-- | src/rcl_prv_discover.erl | 29 | ||||
-rw-r--r-- | src/rcl_prv_overlay.erl | 202 | ||||
-rw-r--r-- | src/rcl_prv_release.erl | 17 | ||||
-rw-r--r-- | src/rcl_state.erl | 52 | ||||
-rw-r--r-- | src/relcool.erl | 61 | ||||
-rw-r--r-- | test/rclt_command_SUITE.erl | 3 | ||||
-rw-r--r-- | test/rclt_discover_SUITE.erl | 11 | ||||
-rw-r--r-- | test/rclt_release_SUITE.erl | 167 |
15 files changed, 455 insertions, 173 deletions
@@ -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 @@ -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". |