From 764abd22dbebebaa5125e5d5061edacd9dd5eb09 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 10 Dec 2012 12:29:17 -0500 Subject: Add wiki link to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b38b860..7f95cb7 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,5 @@ Configuration files # SEE ALSO `reltool` (1). + +[relcool wiki](https://github.com/erlware/relcool/wiki) -- cgit v1.2.3 From e417e4a8adc20d804e2826aad12ce050f4c6ce0d Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 11:04:47 -0500 Subject: change the default verbosity to error --- src/relcool.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relcool.erl b/src/relcool.erl index 0d75c62..d5ddbc5 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -93,7 +93,7 @@ opt_spec_list() -> "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, 2}, "Verbosity level, maybe between 0 and 2"} + {log_level, $V, "verbose", {integer, 0}, "Verbosity level, maybe between 0 and 2"} ]. -spec format_error(Reason::term()) -> iolist(). -- cgit v1.2.3 From 7417cd704fa5e2cb30cfc31e53a74963a06f658c Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 11:06:51 -0500 Subject: fix the documentation for searched directories, fixes #13 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f95cb7..30e5c82 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 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 -d ~/my-dirs --relname foo --relvsn 0.0.1 --target-spec myapp --target-spec getopt>=0.5.1 -o output-dir --targz + relcool -l ~/my-dirs --relname foo --relvsn 0.0.1 --target-spec myapp --target-spec getopt>=0.5.1 -o output-dir --targz The *release-specification-file* is optional but otherwise contains additional specification information for releases. -- cgit v1.2.3 From 8229c7b27f20198b18b1eb67b9b8247282404331 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 15:01:58 -0500 Subject: turn off verbosity in tests --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 2b2f0b3..3294d13 100644 --- a/rebar.config +++ b/rebar.config @@ -21,7 +21,7 @@ %% EUnit ======================================================================= {eunit_opts, - [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. + [{report, {eunit_surefire, [{dir, "."}]}}]}. {cover_enabled, true}. {cover_print_enabled, true}. -- cgit v1.2.3 From 73c1e64c805376460fc2e26db810877cb89bc04b Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 15:02:14 -0500 Subject: make sure that rcl_cmd_args does not create the output dir too early --- src/rcl_cmd_args.erl | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 68bd9ce..d72bce3 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -75,9 +75,6 @@ format_error({invalid_config_file, Config}) -> io_lib:format("Invalid configuration file specified: ~s", [Config]); format_error({failed_to_parse, Spec}) -> io_lib:format("Unable to parse spec ~s", [Spec]); -format_error({unable_to_create_output_dir, OutputDir}) -> - io_lib:format("Unable to create output directory (possible permissions issue): ~s", - [OutputDir]); format_error({not_directory, Dir}) -> io_lib:format("Library directory does not exist: ~s", [Dir]); format_error({invalid_log_level, LogLevel}) -> @@ -148,17 +145,7 @@ convert_goals([RawSpec | Rest], Acc) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_output_dir(Opts, Acc) -> OutputDir = proplists:get_value(output_dir, Opts, "./_rel"), - case filelib:is_dir(OutputDir) of - false -> - case rcl_util:mkdir_p(OutputDir) of - ok -> - create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]); - {error, _} -> - ?RCL_ERROR({unable_to_create_output_dir, OutputDir}) - end; - true -> - create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]) - end. + create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]). -spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). -- cgit v1.2.3 From 4d21d2576afead307717d8f61a3904826f8df0e6 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 15:02:31 -0500 Subject: assembler should create the output dir --- src/rcl_prv_assembler.erl | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index c0f65c9..75a54ec 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -44,11 +44,16 @@ do(State) -> {RelName, RelVsn} = rcl_state:default_release(State), Release = rcl_state:get_release(State, RelName, RelVsn), OutputDir = rcl_state:output_dir(State), - case rcl_release:realized(Release) of - true -> - copy_app_directories_to_output(State, Release, OutputDir); - false -> - ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + 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(). @@ -69,6 +74,9 @@ format_error({release_script_generation_error, 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)]. @@ -76,6 +84,21 @@ format_error({release_script_generation_error, Module, Errors}) -> %%%=================================================================== %%% 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), -- cgit v1.2.3 From 902d2f4718f4eec674252cb5643c0dd137d741e0 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 15:03:49 -0500 Subject: support a setable root directory for the system --- src/rcl_state.erl | 15 ++++++++++++++- src/relcool.erl | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 7397e6f..d324bf9 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -35,6 +35,8 @@ providers/2, sys_config/1, sys_config/2, + root_dir/1, + root_dir/2, add_release/2, get_release/3, update_release/2, @@ -57,6 +59,7 @@ cmd_args/0]). -record(state_t, {log :: rcl_log:t(), + root_dir :: file:name(), caller :: caller(), output_dir :: file:name(), lib_dirs=[] :: [file:name()], @@ -90,9 +93,10 @@ %% @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) -> + {ok, Root} = file:get_cwd(), State0 = #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), - output_dir=filename:absname(proplists:get_value(output_dir, PropList, "")), + 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), goals=proplists:get_value(goals, PropList, []), @@ -100,6 +104,7 @@ new(PropList, Targets) when erlang:is_list(PropList) -> 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), proplists:get_value(relvsn, PropList, undefined)}}, create_logic_providers(State0). @@ -147,6 +152,14 @@ sys_config(#state_t{sys_config=SysConfig}) -> 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}. diff --git a/src/relcool.erl b/src/relcool.erl index d5ddbc5..c6747ca 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -126,6 +126,8 @@ run_providers(State0) -> 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) -- cgit v1.2.3 From c3e728afb67101480d0c7b52c51bc522f08fb08f Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Dec 2012 15:04:12 -0500 Subject: if no config is specified search for a `relcool.config` in the path --- src/rcl_prv_config.erl | 50 +++++++++++++++++++++++++++++++++++++++++++-- src/rcl_state.erl | 5 +++++ test/rclt_command_SUITE.erl | 15 +------------- test/rclt_release_SUITE.erl | 45 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl index ee1c770..7ea9a77 100644 --- a/src/rcl_prv_config.erl +++ b/src/rcl_prv_config.erl @@ -29,8 +29,12 @@ init(State) -> %% populating the state as a result. -spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error(). do(State) -> - ConfigFiles = rcl_state:config_files(State), - lists:foldl(fun load_config/2, {ok, State}, ConfigFiles). + case rcl_state:config_files(State) of + [] -> + search_for_dominating_config(State); + ConfigFiles -> + lists:foldl(fun load_config/2, {ok, State}, ConfigFiles) + end. -spec format_error(Reason::term()) -> iolist(). format_error({consult, ConfigFile, Reason}) -> @@ -42,6 +46,48 @@ format_error({invalid_term, 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, {ok, rcl_state:config_files(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(), {ok, rcl_state:t()} | relcool:error()) -> {ok, rcl_state:t()} | relcool:error(). load_config(_, Err = {error, _}) -> diff --git a/src/rcl_state.erl b/src/rcl_state.erl index d324bf9..bd26a47 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -31,6 +31,7 @@ overrides/2, goals/1, config_files/1, + config_files/2, providers/1, providers/2, sys_config/1, @@ -140,6 +141,10 @@ goals(#state_t{goals=TS}) -> config_files(#state_t{config_files=ConfigFiles}) -> ConfigFiles. +-spec config_files(t(), [file:filename()]) -> t(). +config_files(State, ConfigFiles) -> + State#state_t{config_files=ConfigFiles}. + -spec providers(t()) -> [rcl_provider:t()]. providers(#state_t{providers=Providers}) -> Providers. diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl index 46449e9..1c6accf 100644 --- a/test/rclt_command_SUITE.erl +++ b/test/rclt_command_SUITE.erl @@ -25,7 +25,6 @@ all/0, normal_passing_case/1, lib_fail_case/1, - output_fail_case/1, spec_parse_fail_case/1, config_fail_case/1]). @@ -42,7 +41,7 @@ end_per_suite(_Config) -> ok. all() -> - [normal_passing_case, lib_fail_case, output_fail_case, config_fail_case]. + [normal_passing_case, lib_fail_case, config_fail_case]. normal_passing_case(Config) -> DataDir = proplists:get_value(data_dir, Config), @@ -82,18 +81,6 @@ lib_fail_case(Config) -> ?assertMatch({error, {_, {not_directory, Lib2}}}, rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). - -output_fail_case(Config) -> - DataDir = proplists:get_value(data_dir, Config), - UnwritableDir = filename:join([DataDir, "unwritable"]), - ok = rcl_util:mkdir_p(UnwritableDir), - ok = file:change_mode(UnwritableDir, 8#555), - CanNotCreate = filename:join([UnwritableDir, "out-dir-should-not-create"]), - - CmdLine = ["-o", CanNotCreate], - ?assertMatch({error, {_, {unable_to_create_output_dir, CanNotCreate}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). - spec_parse_fail_case(_Config) -> Spec = "aaeu:3333:33.22a44", CmdLine = ["-g", Spec], diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index c13672a..4ac44d9 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -25,7 +25,8 @@ init_per_testcase/2, all/0, make_release/1, - make_overridden_release/1]). + make_overridden_release/1, + make_implicit_config_release/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -49,7 +50,7 @@ init_per_testcase(_, Config) -> {state, State} | Config]. all() -> - [make_release, make_overridden_release]. + [make_release, make_overridden_release, make_implicit_config_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -136,6 +137,46 @@ make_overridden_release(Config) -> OverrideApp ++ "-" ++ OverrideVsn])), ?assertMatch(OverrideAppDir, Real). +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, []), + [{{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)), + ?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)). %%%=================================================================== -- cgit v1.2.3 From 3102f456473f42b170b0c1a47786831b712b1b08 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 18 Dec 2012 09:59:34 -0500 Subject: fix a bug in the ability to rebuild releases --- src/rcl_prv_assembler.erl | 24 +++++++++++++++--- test/rclt_release_SUITE.erl | 59 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 75a54ec..1ba31f8 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -79,7 +79,11 @@ format_error({unable_to_create_output_dir, OutputDir}) -> [OutputDir]); format_error({release_script_generation_error, Module, Errors}) -> ["Errors generating release \n", - rcl_util:indent(1), Module:format_error(Errors)]. + 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 @@ -125,7 +129,7 @@ copy_app(LibDir, App) -> TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), case rcl_app_info:link(App) of true -> - file:make_symlink(AppDir, TargetDir); + link_directory(AppDir, TargetDir); false -> ec_plists:map(fun(SubDir) -> copy_dir(AppDir, TargetDir, SubDir) @@ -138,6 +142,20 @@ copy_app(LibDir, App) -> "LICENSE"]) end. +link_directory(AppDir, TargetDir) -> + case ec_file:exists(TargetDir) of + true -> + ec_file:remove(TargetDir); + false -> + ok + end, + case file:make_symlink(AppDir, TargetDir) of + {error, Reason} -> + ?RCL_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); + ok -> + ok + end. + copy_dir(AppDir, TargetDir, SubDir) -> SubSource = filename:join(AppDir, SubDir), SubTarget = filename:join(TargetDir, SubDir), @@ -167,7 +185,7 @@ create_release_info(State, Release, OutputDir) -> rcl_release:vsn(Release)]), ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), - case rcl_release:metadata(Release) of + case rcl_release:metadata(Release) of {ok, Meta} -> ok = ec_file:write_term(ReleaseFile, Meta), write_bin_file(State, Release, OutputDir, ReleaseDir); diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 4ac44d9..b37fd12 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -26,6 +26,7 @@ all/0, make_release/1, make_overridden_release/1, + make_rerun_overridden_release/1, make_implicit_config_release/1]). -include_lib("common_test/include/ct.hrl"). @@ -50,7 +51,8 @@ init_per_testcase(_, Config) -> {state, State} | Config]. all() -> - [make_release, make_overridden_release, make_implicit_config_release]. + [make_release, make_overridden_release, make_implicit_config_release, + make_rerun_overridden_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -178,6 +180,61 @@ make_implicit_config_release(Config) -> ?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), + 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, State} = relcool:do(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, + OutputDir, [{OverrideAppName, OverrideAppDir}], + [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)), + ?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). + %%%=================================================================== %%% Helper Functions -- cgit v1.2.3 From 19d05486ac45580e139501fde52f73c9e43eefc0 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 18 Dec 2012 11:15:38 -0500 Subject: make sure that copy errors can be correctly detected --- src/rcl_prv_assembler.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 1ba31f8..99b5eab 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -112,9 +112,9 @@ copy_app_directories_to_output(State, Release, OutputDir) -> (_) -> false end, - ec_plists:map(fun(App) -> - copy_app(LibDir, App) - end, Apps)), + lists:flatten(ec_plists:map(fun(App) -> + copy_app(LibDir, App) + end, Apps))), case Result of [E | _] -> E; -- cgit v1.2.3 From 51b627c68792039c7c7162d354ff0250c7a795b8 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 18 Dec 2012 12:01:29 -0500 Subject: make sure that the app target dir is created befor a copy --- src/rcl_prv_assembler.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 99b5eab..271b0d5 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -131,6 +131,7 @@ copy_app(LibDir, App) -> true -> link_directory(AppDir, TargetDir); false -> + ok = rcl_util:mkdir_p(TargetDir), ec_plists:map(fun(SubDir) -> copy_dir(AppDir, TargetDir, SubDir) end, ["ebin", -- cgit v1.2.3 From 16368e6531d6bdcd6a2f6a92bd18a20c131766f0 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 18 Dec 2012 16:44:44 -0500 Subject: ensure that each directory is explicitly created before a copy occures. --- src/rcl_prv_assembler.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 271b0d5..b8766df 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -168,6 +168,7 @@ copy_dir(AppDir, TargetDir, SubDir) -> false -> ok end, + ok = filelib:ensure_dir(SubTarget), case ec_file:copy(SubSource, SubTarget, [recursive]) of {error, E} -> ?RCL_ERROR({ec_file_error, AppDir, TargetDir, E}); -- cgit v1.2.3 From 3cbd64cce8ce1df48f1982819297bb3ba63c0191 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 21 Dec 2012 10:42:57 -0500 Subject: refactor rcl_prv_assembler for symplicity and reliability --- src/rcl_prv_assembler.erl | 48 +++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index b8766df..a721b12 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -127,29 +127,28 @@ copy_app(LibDir, App) -> AppVsn = rcl_app_info:vsn_as_string(App), AppDir = rcl_app_info:dir(App), TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), + remove_symlink_or_directory(TargetDir), case rcl_app_info:link(App) of true -> link_directory(AppDir, TargetDir); false -> - ok = rcl_util:mkdir_p(TargetDir), - ec_plists:map(fun(SubDir) -> - copy_dir(AppDir, TargetDir, SubDir) - end, ["ebin", - "include", - "priv", - "src", - "c_src", - "README", - "LICENSE"]) + copy_directory(AppDir, TargetDir) end. -link_directory(AppDir, TargetDir) -> - case ec_file:exists(TargetDir) of +remove_symlink_or_directory(TargetDir) -> + case ec_file:is_symlink(TargetDir) of true -> ec_file:remove(TargetDir); false -> - ok - end, + 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}); @@ -157,21 +156,26 @@ link_directory(AppDir, TargetDir) -> 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 -> - case filelib:is_dir(SubTarget) of - true -> - ok = ec_file:remove(SubTarget, [recursive]); - false -> - ok - end, - ok = filelib:ensure_dir(SubTarget), + ok = rcl_util:mkdir_p(SubTarget), case ec_file:copy(SubSource, SubTarget, [recursive]) of {error, E} -> - ?RCL_ERROR({ec_file_error, AppDir, TargetDir, E}); + ?RCL_ERROR({ec_file_error, AppDir, SubTarget, E}); ok -> ok end; -- cgit v1.2.3 From a9b6d65069ceac9b601216c10a0e60c97a80603a Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 21 Dec 2012 10:43:09 -0500 Subject: fix nasty bug in discovery process --- src/rcl_cmd_args.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index d72bce3..dc176be 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -145,7 +145,7 @@ convert_goals([RawSpec | Rest], Acc) -> {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, OutputDir} | Acc]). + 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(). -- cgit v1.2.3 From 2eed239f63a5ae64e99522142a262f0af67425a0 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 26 Dec 2012 20:17:56 -0500 Subject: fix small bug in rcl_app_info defaults --- src/rcl_app_info.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl index bc64e30..b73d609 100644 --- a/src/rcl_app_info.erl +++ b/src/rcl_app_info.erl @@ -63,8 +63,8 @@ vsn :: ec_semver:semver(), dir :: file:name(), link=false :: boolean(), - active_deps :: [atom()], - library_deps :: [atom()]}). + active_deps=[]:: [atom()], + library_deps=[] :: [atom()]}). %%============================================================================ %% types -- cgit v1.2.3 From 713b954082068456db93e2c5f661169550e29fda Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 26 Dec 2012 20:18:18 -0500 Subject: provide utility functions for error decomposition --- src/rcl_util.erl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/rcl_util.erl b/src/rcl_util.erl index f6427ae..f89ba73 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -23,6 +23,8 @@ -export([mkdir_p/1, to_binary/1, + is_error/1, + error_reason/1, indent/1]). -define(ONE_LEVEL_INDENT, " "). @@ -55,6 +57,18 @@ to_binary(String) when erlang:is_list(String) -> to_binary(Bin) when erlang:is_binary(Bin) -> Bin. +%% @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. + + %%%=================================================================== %%% Test Functions -- cgit v1.2.3 From abaaacbfcd0fa186cb05b89d7550a8b1fef7cace Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 26 Dec 2012 20:18:56 -0500 Subject: fix bug in goal spec --- src/rcl_state.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcl_state.erl b/src/rcl_state.erl index bd26a47..21fbbb3 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -133,7 +133,7 @@ output_dir(#state_t{output_dir=OutDir}) -> lib_dirs(#state_t{lib_dirs=LibDir}) -> LibDir. --spec goals(t()) -> [rcl_depsolver:constraints()]. +-spec goals(t()) -> [rcl_depsolver:constraint()]. goals(#state_t{goals=TS}) -> TS. -- cgit v1.2.3 From 078d3c349b3f465dc2f45f0bbfcff297e82074e5 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 26 Dec 2012 20:19:54 -0500 Subject: overlay support This includes support for overlays and fairly complete tests --- rebar.config | 3 + src/rcl_prv_overlay.erl | 324 ++++++++++++++++++++++++++++++++++++++++++++ src/rcl_state.erl | 7 +- test/rclt_release_SUITE.erl | 163 +++++++++++++++++++++- 4 files changed, 492 insertions(+), 5 deletions(-) create mode 100644 src/rcl_prv_overlay.erl diff --git a/rebar.config b/rebar.config index 3294d13..3561991 100644 --- a/rebar.config +++ b/rebar.config @@ -8,6 +8,9 @@ {erlware_commons, ".*", {git, "https://github.com/erlware/erlware_commons.git", {branch, "next"}}}, + {erlydtl, ".*", + {git, "https://github.com/evanmiller/erlydtl.git", + {branch, "master"}}}, {getopt, "", {git, "https://github.com/jcomellas/getopt.git", {branch, "master"}}}]}. diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl new file mode 100644 index 0000000..af914ee --- /dev/null +++ b/src/rcl_prv_overlay.erl @@ -0,0 +1,324 @@ +%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%%% 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]). + +-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_release(State), + Release = rcl_state:get_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) -> + case file:consult(FileName) of + {ok, Terms} -> + do_overlay(State, OverlayVars ++ Terms); + {error, Reason} -> + ?RCL_ERROR({unable_to_read_varsfile, FileName, Reason}) + end. + +-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_files, rcl_state:config_files(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 + {Name0, _} -> + Name0 + end}, + {default_release_version, case rcl_state:default_release(State) of + {_, Vsn0} -> + Vsn0 + end}, + {default_release, case rcl_state:default_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, + 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)) + 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), + 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]), + 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(State, OverlayVars, From, FromTemplateName, + fun(FromFile) -> + file_render_do(State, 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 + 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, + fun(FromFile) -> + file_render_do(State, OverlayVars, To, ToTemplateName, + fun(ToFile) -> + render_template(OverlayVars, + erlang:binary_to_list(FromFile), + ToFile) + end) + end). + +-spec render_template(proplists:proplist(), iolist(), file:name()) -> + 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} -> + case render(TemplateName, OverlayVars) of + {ok, IoData} -> + io:format("Rendering ~p~n", [IoData]), + case filelib:ensure_dir(ToFile) of + ok -> + case file:write_file(ToFile, IoData) of + ok -> + ok; + {error, Reason} -> + ?RCL_ERROR({unable_to_write, ToFile, Reason}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_enclosing_dir, ToFile, Reason}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_render_template, FromFile, Reason}) + end; + {error, Reason} -> + ?RCL_ERROR({unable_to_compile_template, FromFile, Reason}) + end. + +-spec file_render_do(rcl_state:t(), 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) -> + 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); + {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. diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 21fbbb3..eb70ecc 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -280,9 +280,10 @@ 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), - {AssemblerProvider, {ok, State4}} = rcl_provider:new(rcl_prv_assembler, State3), - State4#state_t{providers=[ConfigProvider, DiscoveryProvider, - ReleaseProvider, AssemblerProvider]}. + {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]}. %% @doc config files can come in as either a single file name or as a list of diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index b37fd12..f4f0ecc 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -27,7 +27,8 @@ make_release/1, make_overridden_release/1, make_rerun_overridden_release/1, - make_implicit_config_release/1]). + make_implicit_config_release/1, + overlay_release/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -52,7 +53,7 @@ init_per_testcase(_, Config) -> all() -> [make_release, make_overridden_release, make_implicit_config_release, - make_rerun_overridden_release]. + make_rerun_overridden_release, overlay_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -235,6 +236,129 @@ make_rerun_overridden_release(Config) -> 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"}, + {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"}]}, + {foo_dir, "foodir"}]), + + TemplateFile = filename:join([LibDir1, "test_template"]), + ok = file:write_file(TemplateFile, test_template_contents()), + + 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)), + ?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"]))), + + TemplateData = case file:consult(filename:join([OutputDir, test_template_resolved])) of + {ok, Details} -> + Details; + Error -> + erlang:throw({failed_to_consult, Error}) + end, + + ?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_files, TemplateData)), + ?assertEqual([""], + proplists:get_value(goals, TemplateData)), + ?assertEqual("undefined", + 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)). %%%=================================================================== %%% Helper Functions @@ -278,3 +402,38 @@ create_random_vsn() -> 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_files, [\"{{ config_files|join:\", \" }}\"]}.\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". -- cgit v1.2.3 From e390e61cf7d678eac21b5eae6b64ee82304efa9a Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 27 Dec 2012 09:45:20 -0500 Subject: remove print statements that got left in And made it through code review!!! --- src/rcl_prv_overlay.erl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index af914ee..243df99 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -182,10 +182,8 @@ do_overlay(State, OverlayVars) -> 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)) end. @@ -206,16 +204,12 @@ 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]), case rcl_util:mkdir_p(Absolute) of {error, Error} -> ?RCL_ERROR({unable_to_make_dir, Absolute, Error}); @@ -267,7 +261,6 @@ render_template(OverlayVars, FromFile, ToFile) -> Good when Good =:= ok; ok =:= {ok, TemplateName} -> case render(TemplateName, OverlayVars) of {ok, IoData} -> - io:format("Rendering ~p~n", [IoData]), case filelib:ensure_dir(ToFile) of ok -> case file:write_file(ToFile, IoData) of -- cgit v1.2.3 From b87e030af2e3120ea4a6950c9808285897ce7be9 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 27 Dec 2012 10:18:05 -0500 Subject: splint out render and write so we can reuse the render --- src/rcl_prv_overlay.erl | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 243df99..7e8b369 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -247,38 +247,47 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> fun(FromFile) -> file_render_do(State, OverlayVars, To, ToTemplateName, fun(ToFile) -> - render_template(OverlayVars, - erlang:binary_to_list(FromFile), - ToFile) + write_template(OverlayVars, + erlang:binary_to_list(FromFile), + ToFile) end) end). --spec render_template(proplists:proplist(), iolist(), file:name()) -> +-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 +render_template(OverlayVars, Data) -> + TemplateName = make_template_name("rcl_template_renderer", Data), + case erlydtl:compile(Data, TemplateName) of Good when Good =:= ok; ok =:= {ok, TemplateName} -> case render(TemplateName, OverlayVars) of {ok, 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; {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(), fun((term()) -> {ok, rcl_state:t()} | relcool:error())) -> {ok, rcl_state:t()} | relcool:error(). -- cgit v1.2.3 From 901f020a4e0e5635b2d242f75ead571830395a06 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 27 Dec 2012 11:54:38 -0500 Subject: suport variables in vars files of overlays --- src/rcl_prv_overlay.erl | 48 +++++++++++++++++++++++++++++++++++++++++++-- test/rclt_release_SUITE.erl | 8 ++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 7e8b369..d7fcc1b 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -112,11 +112,55 @@ get_overlay_vars_from_file(State, OverlayVars) -> read_overlay_vars(State, OverlayVars, FileName) -> case file:consult(FileName) 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)}, @@ -258,7 +302,7 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> render_template(OverlayVars, Data) -> TemplateName = make_template_name("rcl_template_renderer", Data), case erlydtl:compile(Data, TemplateName) of - Good when Good =:= ok; ok =:= {ok, TemplateName} -> + Good when Good =:= ok; Good =:= {ok, TemplateName} -> case render(TemplateName, OverlayVars) of {ok, IoData} -> {ok, IoData}; diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index f4f0ecc..890a24b 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -269,6 +269,7 @@ 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"]), @@ -358,7 +359,9 @@ 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)). %%%=================================================================== %%% Helper Functions @@ -436,4 +439,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". -- cgit v1.2.3 From 58a82fd5970aa098826ad65f32cc3dab34fb1da6 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 27 Dec 2012 17:45:32 -0500 Subject: support file->directory copies in overlay --- src/rcl_prv_overlay.erl | 73 ++++++++++++++++++++++++++------------------- test/rclt_release_SUITE.erl | 3 ++ 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index d7fcc1b..aa27bd6 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"). %%============================================================================ @@ -153,14 +155,10 @@ render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc) Error end; render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) -> - render_overlay_vars(OverlayVars, Rest, Acc ++ KeyValue); + 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)}, @@ -226,9 +224,8 @@ do_overlay(State, OverlayVars) -> Overlays -> handle_errors(State, ec_plists:map(fun(Overlay) -> - Res = do_individual_overlay(State, OverlayVars, - Overlay), - Res + do_individual_overlay(State, OverlayVars, + Overlay) end, Overlays)) end. @@ -252,8 +249,7 @@ do_individual_overlay(State, OverlayVars, {mkdir, Dir}) -> {ok, ModuleName} -> case render(ModuleName, OverlayVars) of {ok, IoList} -> - Absolute = filename:absname(filename:join(rcl_state:root_dir(State), - erlang:iolist_to_binary(IoList))), + Absolute = absolutize(State, IoList), case rcl_util:mkdir_p(Absolute) of {error, Error} -> ?RCL_ERROR({unable_to_make_dir, Absolute, Error}); @@ -269,34 +265,48 @@ 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) -> write_template(OverlayVars, - erlang:binary_to_list(FromFile), - ToFile) + erlang:binary_to_list(absolutize(State, FromFile)), + absolutize(State, ToFile)) end) end). +-spec copy_to(rcl_state:t(), file:name(), file:name()) -> ok | relcool:error(). +copy_to(State, FromFile0, ToFile0) -> + ToFile1 = absolutize(State, ToFile0), + FromFile1 = absolutize(State, FromFile0), + ToFile2 = case re:run(ToFile0, ?DIRECTORY_RE) of + nomatch -> + filelib:ensure_dir(ToFile1), + ToFile1; + _ -> + 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; + {error, Err} -> + ?RCL_ERROR({copy_failed, + FromFile1, + ToFile1, Err}) + end. + -spec render_template(proplists:proplist(), iolist()) -> ok | relcool:error(). render_template(OverlayVars, Data) -> @@ -331,18 +341,15 @@ write_template(OverlayVars, FromFile, ToFile) -> 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; @@ -368,3 +375,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/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 890a24b..06644ee 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -260,6 +260,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"}, @@ -293,6 +295,7 @@ 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 {ok, Details} -> -- cgit v1.2.3 From 8c22ae33056a406fe2baf180d16e3fb090638508 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Dec 2012 10:08:26 -0500 Subject: make overlays synchronous People seem to expect overlays to happen in order. This patch changes overlays such that they happen one at a time. --- src/rcl_prv_overlay.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index aa27bd6..160629b 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -223,10 +223,10 @@ do_overlay(State, OverlayVars) -> {ok, State}; Overlays -> handle_errors(State, - ec_plists:map(fun(Overlay) -> - do_individual_overlay(State, OverlayVars, - Overlay) - end, Overlays)) + lists:map(fun(Overlay) -> + do_individual_overlay(State, OverlayVars, + Overlay) + end, Overlays)) end. -spec handle_errors(rcl_state:t(), [ok | relcool:error()]) -> -- cgit v1.2.3 From b5b6cf855b3bceaac76ccd330d64cfeb2b1f4c14 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Dec 2012 10:21:37 -0500 Subject: make directory checking more robust --- src/rcl_prv_overlay.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 160629b..9670138 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -289,11 +289,11 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> copy_to(State, FromFile0, ToFile0) -> ToFile1 = absolutize(State, ToFile0), FromFile1 = absolutize(State, FromFile0), - ToFile2 = case re:run(ToFile0, ?DIRECTORY_RE) of - nomatch -> + 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))) @@ -307,6 +307,16 @@ copy_to(State, FromFile0, ToFile0) -> ToFile1, Err}) 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) -> -- cgit v1.2.3 From ae2e47e811ef113c0e6ab1df2bb66a4917f7ea07 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Dec 2012 11:47:35 -0500 Subject: support the ability to *not* autogenerate start scripts --- src/rcl_prv_assembler.erl | 17 +++++++++++----- test/rclt_release_SUITE.erl | 47 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 8 deletions(-) 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/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 06644ee..848c129 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -25,6 +25,7 @@ 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, @@ -52,8 +53,9 @@ 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_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -90,6 +92,46 @@ 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_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), @@ -171,7 +213,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)), -- cgit v1.2.3 From 39ff3c87aa0d6444e5c3d9ae03538d0f212d81e6 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Dec 2012 12:14:29 -0500 Subject: add missing erlydtl to the escript created --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]}. -- cgit v1.2.3 From 3e295e146c960252af0008f1f13a7e40bcbdb929 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Dec 2012 13:00:34 -0500 Subject: make sure file permissions are set correctly in overlay template/copy --- src/rcl_prv_overlay.erl | 4 ++++ test/rclt_release_SUITE.erl | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 9670138..b854650 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -300,6 +300,8 @@ copy_to(State, FromFile0, ToFile0) -> 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, @@ -340,6 +342,8 @@ write_template(OverlayVars, FromFile, ToFile) -> 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}) diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 848c129..593944e 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -34,6 +34,7 @@ -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}}]. @@ -317,6 +318,8 @@ overlay_release(Config) -> 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")]), @@ -338,12 +341,14 @@ overlay_release(Config) -> ?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)), -- cgit v1.2.3 From 8cbfdd28679872a1791fee615585aec6c03dd5f9 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 16:59:44 -0500 Subject: support 'to' dirs for mkdirs, templates, and copies being based on output dir --- src/rcl_prv_overlay.erl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index b854650..0e9781b 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -249,7 +249,9 @@ do_individual_overlay(State, OverlayVars, {mkdir, Dir}) -> {ok, ModuleName} -> case render(ModuleName, OverlayVars) of {ok, IoList} -> - Absolute = absolutize(State, 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}); @@ -279,15 +281,20 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> fun(FromFile) -> file_render_do(OverlayVars, To, ToTemplateName, fun(ToFile) -> + FromFile0 = absolutize(State, + filename:join(rcl_state:output_dir(State), + erlang:iolist_to_binary(FromFile))), + FromFile1 = erlang:binary_to_list(FromFile0), write_template(OverlayVars, - erlang:binary_to_list(absolutize(State, FromFile)), + FromFile1, absolutize(State, ToFile)) end) end). -spec copy_to(rcl_state:t(), file:name(), file:name()) -> ok | relcool:error(). copy_to(State, FromFile0, ToFile0) -> - ToFile1 = absolutize(State, ToFile0), + ToFile1 = absolutize(State, filename:join(rcl_state:output_dir(State), + erlang:iolist_to_binary(ToFile0))), FromFile1 = absolutize(State, FromFile0), ToFile2 = case is_directory(ToFile0, ToFile1) of false -> -- cgit v1.2.3 From 306d584398696d08e3ca26d6864fa71cdcfe4012 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 17:34:25 -0500 Subject: only allow relcool to take a single config --- src/rcl_cmd_args.erl | 13 +++++++++---- src/relcool.erl | 2 +- test/rclt_command_SUITE.erl | 3 +-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index dc176be..3bb9f41 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}) -> diff --git a/src/relcool.erl b/src/relcool.erl index c6747ca..fb17711 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -43,7 +43,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([], []), 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)), -- cgit v1.2.3 From fbf58da536939dece9a6707b7ff39e12bf377d1c Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 17:59:26 -0500 Subject: fix bug in usage of root dir in prv discover --- src/rcl_prv_discover.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 0a12d24..6ff50ed 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -123,13 +123,13 @@ add_rebar_deps_dir(State, LibDirs) -> 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(), + Root = rcl_state:root_dir(State), - RebarConfig = filename:join([Cwd, "rebar.config"]), - DepsDir = filename:join([Cwd, "deps"]), + RebarConfig = filename:join([Root, "rebar.config"]), + DepsDir = filename:join([Root, "deps"]), case filelib:is_regular(RebarConfig) andalso filelib:is_dir(DepsDir) of true -> - add_system_lib_dir(State, [filename:absname(Cwd) | LibDirs]); + add_system_lib_dir(State, [filename:absname(Root) | LibDirs]); false -> add_system_lib_dir(State, LibDirs) end -- cgit v1.2.3 From 78ec90ae6084f44b5379bd4e9eebb79746f4a595 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 19:15:01 -0500 Subject: support only one config file in the system --- src/rcl_prv_config.erl | 14 ++++++------- src/rcl_prv_overlay.erl | 3 ++- src/rcl_state.erl | 48 ++++++++++++--------------------------------- src/relcool.erl | 4 ++-- test/rclt_release_SUITE.erl | 6 +++--- 5 files changed, 26 insertions(+), 49 deletions(-) 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_overlay.erl b/src/rcl_prv_overlay.erl index 0e9781b..866ace5 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -196,7 +196,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)}, @@ -295,6 +295,7 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> copy_to(State, FromFile0, ToFile0) -> ToFile1 = absolutize(State, filename:join(rcl_state:output_dir(State), erlang:iolist_to_binary(ToFile0))), + FromFile1 = absolutize(State, FromFile0), ToFile2 = case is_directory(ToFile0, ToFile1) of false -> diff --git a/src/rcl_state.erl b/src/rcl_state.erl index eb70ecc..28effbd 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), @@ -137,13 +137,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 +243,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 +264,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 +275,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 fb17711..9babdb2 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -72,7 +72,7 @@ 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) -> +do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> State = rcl_state:new([{relname, RelName}, {relvsn, RelVsn}, {goals, Goals}, @@ -80,7 +80,7 @@ do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Configs) -> {output_dir, OutputDir}, {lib_dirs, LibDirs}, {log, rcl_log:new(LogLevel)}], - Configs), + Config), run_relcool_process(rcl_state:caller(State, api)). diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 593944e..bcdf354 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -391,7 +391,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", @@ -457,7 +457,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" @@ -479,7 +479,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" -- cgit v1.2.3 From 7313be05d2805a3cf699e25d160401fd95635e33 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 19:34:34 -0500 Subject: support config relative directories in templates and copy --- src/rcl_prv_overlay.erl | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 866ace5..e792a4f 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -281,22 +281,27 @@ do_individual_overlay(State, OverlayVars, {template, From, To}) -> fun(FromFile) -> file_render_do(OverlayVars, To, ToTemplateName, fun(ToFile) -> + RelativeRoot = get_relative_root(State), FromFile0 = absolutize(State, - filename:join(rcl_state:output_dir(State), + filename:join(RelativeRoot, erlang:iolist_to_binary(FromFile))), FromFile1 = erlang:binary_to_list(FromFile0), write_template(OverlayVars, FromFile1, - absolutize(State, ToFile)) + 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, FromFile0), + FromFile1 = absolutize(State, filename:join(RelativeRoot, + erlang:iolist_to_binary(FromFile0))), ToFile2 = case is_directory(ToFile0, ToFile1) of false -> filelib:ensure_dir(ToFile1), @@ -317,6 +322,14 @@ copy_to(State, FromFile0, ToFile0) -> 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 -- cgit v1.2.3 From 60e04b48942a8d2e0d401ae5deb84e4dc61d05f5 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 30 Dec 2012 20:01:39 -0500 Subject: support the ability to specify a root directory --- README.md | 3 +++ src/rcl_cmd_args.erl | 15 ++++++++++++++- src/relcool.erl | 25 +++++++++++++++++++++---- test/rclt_release_SUITE.erl | 8 +++++--- 4 files changed, 43 insertions(+), 8 deletions(-) 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/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 3bb9f41..9d829d3 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -160,9 +160,22 @@ 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(), + {ok, [{root_dir, Cwd} | Acc]}; + _ -> + {ok, [{root_dir, Dir} | Acc]} + end. + + -spec check_lib_dirs([string()]) -> ok | relcool:error(). check_lib_dirs([]) -> ok; diff --git a/src/relcool.erl b/src/relcool.erl index 9babdb2..f240553 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]). @@ -60,10 +61,25 @@ main(Args) -> %% @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). + {ok, Cwd} = file:get_cwd(), + do(Cwd, 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 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 +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,13 +88,14 @@ 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, Config) -> +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), run_relcool_process(rcl_state:caller(State, api)). @@ -93,8 +110,8 @@ opt_spec_list() -> "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"} - ]. + {log_level, $V, "verbose", {integer, 0}, "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}) -> diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index bcdf354..e92772e 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -166,7 +166,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)), @@ -255,12 +256,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]), -- cgit v1.2.3 From 059d9f6d28d8f4e963f556401e23eed44fc44b9d Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Mon, 31 Dec 2012 08:56:02 -0600 Subject: use relative root for overlay vars file path --- src/rcl_prv_overlay.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index e792a4f..94b5f3d 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -112,7 +112,9 @@ 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} -> case render_overlay_vars(OverlayVars, Terms, []) of {ok, NewTerms} -> -- cgit v1.2.3 From a0bd0b37822999f87786d670529624594c82b0a8 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 31 Dec 2012 15:19:18 -0500 Subject: fix bug in goalless configs --- src/rcl_prv_release.erl | 17 ++++++++++++----- test/rclt_release_SUITE.erl | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) 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/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index e92772e..baf2fcd 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -29,7 +29,8 @@ make_overridden_release/1, make_rerun_overridden_release/1, make_implicit_config_release/1, - overlay_release/1]). + overlay_release/1, + make_goalless_release/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -56,7 +57,7 @@ init_per_testcase(_, Config) -> all() -> [make_release, make_scriptless_release, make_overridden_release, make_implicit_config_release, make_rerun_overridden_release, - overlay_release]. + overlay_release, make_goalless_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -262,7 +263,7 @@ make_rerun_overridden_release(Config) -> [ConfigFile]), %% Now we run it again to see if it failse. - {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, + {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], [ConfigFile]), @@ -414,6 +415,32 @@ overlay_release(Config) -> ?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])). + %%%=================================================================== %%% Helper Functions %%%=================================================================== -- cgit v1.2.3 From bb1fbe04e7f44b7b88176cc26537541330248a51 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 2 Jan 2013 15:51:26 -0500 Subject: add tests to try and cover unexpected failures --- test/rclt_release_SUITE.erl | 63 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index baf2fcd..9c9c70c 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -30,7 +30,9 @@ make_rerun_overridden_release/1, make_implicit_config_release/1, overlay_release/1, - make_goalless_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"). @@ -57,7 +59,8 @@ init_per_testcase(_, Config) -> all() -> [make_release, make_scriptless_release, make_overridden_release, make_implicit_config_release, make_rerun_overridden_release, - overlay_release, make_goalless_release]. + overlay_release, make_goalless_release, make_depfree_release, + make_invalid_config_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -94,6 +97,33 @@ 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}) -> @@ -441,6 +471,35 @@ make_goalless_release(Config) -> 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 %%%=================================================================== -- cgit v1.2.3 From 344fa86b23205eebc3233cf9c69a8e9e145714ac Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 4 Jan 2013 09:55:22 -0500 Subject: minor format refactoring --- src/rcl_log.erl | 11 ++++++++--- src/relcool.erl | 32 ++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 13 deletions(-) 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/relcool.erl b/src/relcool.erl index f240553..9fb9240 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -36,6 +36,7 @@ %%============================================================================ -type error() :: {error, {Module::module(), Reason::term()}}. +-type goal() :: string() | binary() | rcl_depsolver:constraint(). %%============================================================================ %% API @@ -60,9 +61,12 @@ 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) -> +-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, [], Configs). + do(Cwd, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Config). %% @doc provides an API to run the Relcool process from erlang applications %% @@ -74,6 +78,9 @@ do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> %% @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). @@ -88,6 +95,9 @@ do(RootDir, 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 +-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}, @@ -104,13 +114,17 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con -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"}, + {log_level, $V, "verbose", {integer, 0}, + "Verbosity level, maybe between 0 and 2"}, {root_dir, $r, "root", string, "The project root directory"}]. -spec format_error(Reason::term()) -> iolist(). @@ -120,7 +134,6 @@ format_error({invalid_return_value, Provider, Value}) -> format_error({error, {Module, Reason}}) -> io_lib:format("~s~n", [Module:format_error(Reason)]). - %%============================================================================ %% internal api %%============================================================================ @@ -187,7 +200,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)), -- cgit v1.2.3 From 3a5cb6adf215e760068c9d3e7a07eb4f971cabf0 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 4 Jan 2013 10:33:54 -0500 Subject: provide the erlang lib dir and cwd as working libraries This happens for every call unless the user passes --disable-default-libs to relcool or {disable_default_libs, true} in the relcool config. --- src/rcl_cmd_args.erl | 9 +++++++-- src/rcl_prv_discover.erl | 29 ++++++++++------------------- src/rcl_state.erl | 4 +++- src/relcool.erl | 6 ++++-- test/rclt_discover_SUITE.erl | 11 +++++------ 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 9d829d3..6340201 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -170,11 +170,16 @@ create_root_dir(Opts, Acc) -> case Dir of undefined -> {ok, Cwd} = file:get_cwd(), - {ok, [{root_dir, Cwd} | Acc]}; + create_disable_default_libs(Opts, [{root_dir, Cwd} | Acc]); _ -> - {ok, [{root_dir, Dir} | 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([]) -> diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 6ff50ed..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. - Root = rcl_state:root_dir(State), - - RebarConfig = filename:join([Root, "rebar.config"]), - DepsDir = filename:join([Root, "deps"]), - case filelib:is_regular(RebarConfig) andalso filelib:is_dir(DepsDir) of - true -> - add_system_lib_dir(State, [filename:absname(Root) | 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_state.erl b/src/rcl_state.erl index 28effbd..842b635 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -108,7 +108,9 @@ new(PropList, Target) 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()}]. diff --git a/src/relcool.erl b/src/relcool.erl index 9fb9240..a6b30a3 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -113,8 +113,7 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con -spec opt_spec_list() -> [getopt:option_spec()]. opt_spec_list() -> - [ - {relname, $n, "relname", string, + [{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, @@ -123,6 +122,9 @@ opt_spec_list() -> "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, 0}, "Verbosity level, maybe between 0 and 2"}, {root_dir, $r, "root", string, "The project root directory"}]. 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) -> -- cgit v1.2.3 From 47e700e7ad364dc4d919bc5b16c18e71b4a0892d Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 4 Jan 2013 10:54:21 -0500 Subject: make common_test support explicit The rebar support for common test is very, very buggy and it fails often without common_test failing due to how rebar integrates with common test. This patch takes the responsability for running common test from rebar and runs it directly. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 -- cgit v1.2.3 From f1632c3282d719cd5cbb46299f4f0557653ba50f Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 4 Jan 2013 11:02:46 -0500 Subject: change the default log level to info This makes it a bit more comforting to the user (and me as well). With this the user sees a short description of the release. --- src/relcool.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relcool.erl b/src/relcool.erl index a6b30a3..974b19f 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -125,7 +125,7 @@ opt_spec_list() -> {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, 0}, + {log_level, $V, "verbose", {integer, 1}, "Verbosity level, maybe between 0 and 2"}, {root_dir, $r, "root", string, "The project root directory"}]. -- cgit v1.2.3 From e02d59de76a2f0c0e0311f22e4779652a1375ee9 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 24 Jan 2013 20:59:21 -0800 Subject: fix and normalize module headers --- src/rcl_app_info.erl | 2 +- src/rcl_cmd_args.erl | 2 +- src/rcl_goal_utils.erl | 2 +- src/rcl_log.erl | 2 +- src/rcl_provider.erl | 16 ++++++++++++++++ src/rcl_prv_assembler.erl | 2 +- src/rcl_prv_config.erl | 16 ++++++++++++++++ src/rcl_prv_discover.erl | 2 +- src/rcl_prv_overlay.erl | 2 +- src/rcl_prv_release.erl | 2 +- src/rcl_release.erl | 2 +- src/rcl_state.erl | 2 +- src/rcl_topo.erl | 17 ++++++++++++++++- src/rcl_util.erl | 2 +- src/relcool.erl | 2 +- test/rclt_command_SUITE.erl | 2 +- test/rclt_discover_SUITE.erl | 2 +- test/rclt_goal.erl | 2 +- test/rclt_release_SUITE.erl | 2 +- 19 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl index b73d609..f2afa0b 100644 --- a/src/rcl_app_info.erl +++ b/src/rcl_app_info.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 6340201..99a3d7d 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_goal_utils.erl b/src/rcl_goal_utils.erl index 3627b9c..6065e5c 100644 --- a/src/rcl_goal_utils.erl +++ b/src/rcl_goal_utils.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_log.erl b/src/rcl_log.erl index a24b1c0..71c0b5d 100644 --- a/src/rcl_log.erl +++ b/src/rcl_log.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_provider.erl b/src/rcl_provider.erl index c3ef434..750b96e 100644 --- a/src/rcl_provider.erl +++ b/src/rcl_provider.erl @@ -1,3 +1,19 @@ +%% -*- 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. diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 61f6dad..5488617 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl index f87c467..5d51300 100644 --- a/src/rcl_prv_config.erl +++ b/src/rcl_prv_config.erl @@ -1,3 +1,19 @@ +%% -*- 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. diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 4e7ce72..595902a 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_prv_overlay.erl b/src/rcl_prv_overlay.erl index 94b5f3d..40471ea 100644 --- a/src/rcl_prv_overlay.erl +++ b/src/rcl_prv_overlay.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl index 8a86e02..a98048f 100644 --- a/src/rcl_prv_release.erl +++ b/src/rcl_prv_release.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_release.erl b/src/rcl_release.erl index aa5de72..560a555 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 842b635..a2e6c66 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/rcl_topo.erl b/src/rcl_topo.erl index ec67b56..462b7c5 100644 --- a/src/rcl_topo.erl +++ b/src/rcl_topo.erl @@ -1,4 +1,19 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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 diff --git a/src/rcl_util.erl b/src/rcl_util.erl index f89ba73..06d957d 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -1,4 +1,4 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/src/relcool.erl b/src/relcool.erl index 974b19f..42923c4 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl index bd99da0..78f05fc 100644 --- a/test/rclt_command_SUITE.erl +++ b/test/rclt_command_SUITE.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl index 6b61840..f8dc8a3 100644 --- a/test/rclt_discover_SUITE.erl +++ b/test/rclt_discover_SUITE.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/test/rclt_goal.erl b/test/rclt_goal.erl index 9bf6028..20fb5e5 100644 --- a/test/rclt_goal.erl +++ b/test/rclt_goal.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 9c9c70c..23cdb9e 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -1,4 +1,4 @@ -%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%% -*- 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, -- cgit v1.2.3 From 6c90453d79cd1e8cf35cb4f6ad4ee51d7be39e50 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 24 Jan 2013 21:03:53 -0800 Subject: convert relcool to take a config as a opt argument adds `-c ` or `--config ` instead of passing the config file as an argument. --- README.md | 5 ++- src/rcl_cmd_args.erl | 78 ++++++++++++++++++++++++++------------------ src/rcl_state.erl | 12 ++++--- src/relcool.erl | 8 +++-- test/rclt_command_SUITE.erl | 2 +- test/rclt_discover_SUITE.erl | 2 +- test/rclt_release_SUITE.erl | 24 +++++++------- 7 files changed, 77 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 99fef81..5407002 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 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 -l ~/my-dirs --relname foo --relvsn 0.0.1 --target-spec myapp --target-spec getopt>=0.5.1 -o output-dir --targz + 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 The *release-specification-file* is optional but otherwise contains additional specification information for releases. @@ -43,6 +43,9 @@ additional specification information for releases. -V *INTEGER*, \--verbose *INTEGER* : The verbosity level of the system. Valid values are 1 - 3 +-c *INTEGER*, \--config *INTEGER* +: The custom config file for the relcool system + # CONFIGURATION FILES Configuration files diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 99a3d7d..973abe3 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -38,18 +38,17 @@ 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, - [{relname, RelName}, - {relvsn, RelVsn}]) of - Error = {error, _} -> - Error; - {ok, CommandLineConfig} -> - case validate_configs(Target) of + case convert_target(Target) of + {ok, AtomizedTarget} -> + case create_log(Opts, [{relname, RelName}, + {relvsn, RelVsn}]) of Error = {error, _} -> Error; - {ok, Configs} -> - {ok, rcl_state:new(CommandLineConfig, Configs)} - end + {ok, CommandLineConfig} -> + handle_config(Opts, AtomizedTarget, CommandLineConfig) + end; + Error -> + Error end; args2state({ok, {_Opts, Targets}}) -> ?RCL_ERROR({invalid_targets, Targets}). @@ -84,32 +83,47 @@ 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]). + " range 0..2", [LogLevel]); +format_error({invalid_target, Target}) -> + io_lib:format("Invalid action specified: ~s", [Target]). + %%%=================================================================== %%% Internal Functions %%%=================================================================== --spec validate_configs([file:filename()]) -> - {ok, [file:filename()]} | relcool:error(). -validate_configs(Configs) -> - Result = - lists:foldl(fun(_Config, Err = {error, _}) -> - Err; - (Config, Acc) -> - case filelib:is_regular(Config) of - true -> - [filename:absname(Config) | Acc]; - false -> - ?RCL_ERROR({invalid_config_file, Config}) - end - end, [], Configs), - case Result of - {error, _} -> - Result; - _ -> - %% Order may be important so lets make sure they remain in the same - %% order they came in as - {ok, lists:reverse(Result)} +-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 -> + filename:absname(Config); + false -> + ?RCL_ERROR({invalid_config_file, Config}) end. -spec create_log([getopt:option()], rcl_state:cmd_args()) -> diff --git a/src/rcl_state.erl b/src/rcl_state.erl index a2e6c66..ecf3115 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -62,6 +62,7 @@ -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(), @@ -92,14 +93,17 @@ %% API %%============================================================================ %% @doc Create a new 'log level' for the system --spec new(proplists:proplist(), [file:filename()] | file:filename()) -> t(). -new(PropList, Target) when erlang:is_list(PropList) -> +-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=proplists:get_value(lib_dirs, PropList, ""), - config_file=Target, + config_file=proplists:get_value(config, PropList, ""), + action = Target, goals=proplists:get_value(goals, PropList, []), providers = [], releases=ec_dictionary:new(ec_dict), @@ -286,7 +290,7 @@ create_logic_providers(State0) -> new_test() -> LogState = rcl_log:new(error), - RCLState = new([{log, LogState}], []), + RCLState = new([{log, LogState}], release), ?assertMatch(LogState, log(RCLState)). -endif. diff --git a/src/relcool.erl b/src/relcool.erl index 42923c4..83886c5 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -48,7 +48,7 @@ main(Args) -> {ok, State} -> run_relcool_process(rcl_state:caller(State, command_line)); Error={error, _} -> - report_error(rcl_state:caller(rcl_state:new([], []), + report_error(rcl_state:caller(rcl_state:new([], undefined), command_line), Error) end. @@ -106,8 +106,9 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con {output_dir, OutputDir}, {lib_dirs, LibDirs}, {root_dir, RootDir}, - {log, rcl_log:new(LogLevel)}], - Config), + {log, rcl_log:new(LogLevel)}, + {config, Config}], + release), run_relcool_process(rcl_state:caller(State, api)). @@ -127,6 +128,7 @@ 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"}, + {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(). diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl index 78f05fc..3d06dad 100644 --- a/test/rclt_command_SUITE.erl +++ b/test/rclt_command_SUITE.erl @@ -88,6 +88,6 @@ spec_parse_fail_case(_Config) -> config_fail_case(_Config) -> ConfigFile = "does-not-exist", - CmdLine = [ConfigFile], + CmdLine = ["-c", ConfigFile], ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl index f8dc8a3..4b6cb2a 100644 --- a/test/rclt_discover_SUITE.erl +++ b/test/rclt_discover_SUITE.erl @@ -46,7 +46,7 @@ init_per_testcase(_, Config) -> 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]}], []), + State = rcl_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), [{lib1, LibDir1}, {lib2, LibDir2}, {state, State} | Config]. diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 23cdb9e..95aa385 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -52,7 +52,7 @@ 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]}], []), + State = rcl_state:new([{lib_dirs, [LibDir1]}], release), [{lib1, LibDir1}, {state, State} | Config]. @@ -86,7 +86,7 @@ make_release(Config) -> OutputDir = filename:join([proplists:get_value(data_dir, Config), create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile]), + 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)), @@ -122,7 +122,7 @@ make_invalid_config_release(Config) -> create_random_name("relcool-output")]), {error, {rcl_prv_config, {consult, _, _}}} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile]). + OutputDir, ConfigFile). make_scriptless_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -149,7 +149,7 @@ make_scriptless_release(Config) -> OutputDir = filename:join([proplists:get_value(data_dir, Config), create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile]), + 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"]))), @@ -200,7 +200,7 @@ make_overridden_release(Config) -> {ok, Cwd} = file:get_cwd(), {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, OutputDir, [{OverrideAppName, OverrideAppDir}], - [ConfigFile]), + 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)), @@ -289,13 +289,13 @@ make_rerun_overridden_release(Config) -> create_random_name("relcool-output")]), {ok, Cwd} = file:get_cwd(), {ok, State} = relcool:do(Cwd, undefined, undefined, [], [LibDir1], 2, - OutputDir, [{OverrideAppName, OverrideAppDir}], - [ConfigFile]), + OutputDir, [{OverrideAppName, OverrideAppDir}], + ConfigFile), %% Now we run it again to see if it failse. {ok, State} = relcool:do(Cwd,undefined, undefined, [], [LibDir1], 2, - OutputDir, [{OverrideAppName, OverrideAppDir}], - [ConfigFile]), + OutputDir, [{OverrideAppName, OverrideAppDir}], + ConfigFile), [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), AppSpecs = rcl_release:applications(Release), @@ -358,7 +358,7 @@ overlay_release(Config) -> create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile]), + OutputDir, ConfigFile), [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)), AppSpecs = rcl_release:applications(Release), @@ -469,7 +469,7 @@ make_goalless_release(Config) -> create_random_name("relcool-output")]), ?assertMatch({error,{rcl_prv_release,no_goals_specified}}, relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile])). + OutputDir, ConfigFile)). make_depfree_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -494,7 +494,7 @@ make_depfree_release(Config) -> OutputDir = filename:join([proplists:get_value(data_dir, Config), create_random_name("relcool-output")]), {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2, - OutputDir, [ConfigFile]), + 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)), -- cgit v1.2.3 From 58f0df46df654f6221aff6534477b5df1076e0fa Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 24 Jan 2013 21:04:04 -0800 Subject: fix a bug in running ct tests --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 1cf2e23..a7e9bb6 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ eunit: compile clean-common-test-data $(REBAR) skip_deps=true eunit ct: compile clean-common-test-data + mkdir -p $(CURDIR) logs ct_run -pa $(CURDIR)/ebin \ -pa $(CURDIR)/deps/*/ebin \ -logdir $(CURDIR)/logs \ -- cgit v1.2.3 From 4025eca47e6f80316e7f4c63efc93e7195f419e4 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 25 Jan 2013 14:10:52 -0800 Subject: support the discover task searching the output dir This has the potential to save a lot of time and make future release upgrading much simpler for the user. --- src/rcl_prv_assembler.erl | 10 ++++++++++ src/rcl_prv_discover.erl | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl index 5488617..63bd167 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -127,6 +127,16 @@ copy_app(LibDir, 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 -> diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 595902a..862bd6e 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -42,14 +42,13 @@ init(State) -> %% looking for OTP Applications -spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). do(State) -> - OutputDir = rcl_state:output_dir(State), LibDirs = get_lib_dirs(State), 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, OutputDir). + resolve_app_metadata(State, LibDirs). -spec format_error([ErrorDetail::term()]) -> iolist(). format_error(ErrorDetails) @@ -59,10 +58,9 @@ format_error(ErrorDetails) %%%=================================================================== %%% Internal Functions %%%=================================================================== -resolve_app_metadata(State, LibDirs, OutputDir) -> +resolve_app_metadata(State, LibDirs) -> AppMeta0 = lists:flatten(ec_plists:map(fun(LibDir) -> - discover_dir([OutputDir], - LibDir) + discover_dir([], LibDir) end, LibDirs)), AppMeta1 = setup_overrides(State, AppMeta0), -- cgit v1.2.3 From 14dc28856e0f9f16a406cdf2a16a603276efe273 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 25 Jan 2013 16:55:48 -0800 Subject: move app discovery to a dedicated module --- src/rcl_app_discovery.erl | 223 ++++++++++++++++++++++++++++++++++++++++++++++ src/rcl_prv_discover.erl | 189 +++------------------------------------ 2 files changed, 234 insertions(+), 178 deletions(-) create mode 100644 src/rcl_app_discovery.erl diff --git a/src/rcl_app_discovery.erl b/src/rcl_app_discovery.erl new file mode 100644 index 0000000..20df1cf --- /dev/null +++ b/src/rcl_app_discovery.erl @@ -0,0 +1,223 @@ +%% -*- 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(ec_plists:map(fun(LibDir) -> + discover_dir([], LibDir) + end, LibDirs)), + AppMeta1 = setup_overrides(State, AppMeta0), + + Errors = [case El of + {error, Ret} -> Ret; + _ -> El + end + || El <- AppMeta1, + case El of + {error, _} -> + true; + _ -> + false + end], + + case Errors of + [] -> + AppMeta2 = lists:flatten(AppMeta1), + 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 <- AppMeta2]] + end), + {ok, AppMeta2}; + _ -> + ?RCL_ERROR(Errors) + end. + +app_name({error, _}) -> + undefined; +app_name(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 + [] -> + {error, {invalid_override, AppName, FileName}}; + Error = {error, _} -> + Error; + App -> + 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()], + file:name()) -> + [rcl_app_info:t() | {error, Reason::term()}] | + rcl_app_info:t() | {error, Reason::term()}. +discover_dir(IgnoreDirs, File) -> + case (not lists:member(File, IgnoreDirs)) + andalso filelib:is_dir(File) of + true -> + case file:list_dir(File) of + {error, Reason} -> + {error, {accessing, File, Reason}}; + {ok, List} -> + ec_plists:map(fun(LibDir) -> + discover_dir(IgnoreDirs, LibDir) + end, [filename:join([File, Dir]) || Dir <- List]) + end; + false -> + is_valid_otp_app(File) + end. + +-spec is_valid_otp_app(file:name()) -> rcl_app_info:t() | {error, Reason::term()} | []. +is_valid_otp_app(File) -> + %% Is this an ebin dir? + EbinDir = filename:dirname(File), + case filename:basename(EbinDir) of + "ebin" -> + case lists:suffix(".app", File) of + true -> + has_at_least_one_beam(EbinDir, File); + false -> + [] + end; + _ -> + [] + end. + +-spec has_at_least_one_beam(file:name(), file:filename()) -> + 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()) -> + 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()) -> + 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} -> + 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_prv_discover.erl b/src/rcl_prv_discover.erl index 862bd6e..1ffbece 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -43,71 +43,22 @@ init(State) -> -spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). do(State) -> LibDirs = get_lib_dirs(State), - 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). + case rcl_app_discovery:do(State, LibDirs) of + {ok, AppMeta} -> + {ok, rcl_state:available_apps(State, AppMeta)}; + Error -> + Error + end. --spec format_error([ErrorDetail::term()]) -> iolist(). -format_error(ErrorDetails) - when erlang:is_list(ErrorDetails) -> - [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. +%% @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 %%%=================================================================== -resolve_app_metadata(State, LibDirs) -> - AppMeta0 = lists:flatten(ec_plists:map(fun(LibDir) -> - discover_dir([], LibDir) - end, LibDirs)), - AppMeta1 = setup_overrides(State, AppMeta0), - - Errors = [case El of - {error, Ret} -> Ret; - _ -> El - end - || El <- AppMeta1, - case El of - {error, _} -> - true; - _ -> - false - end], - - case Errors of - [] -> - AppMeta2 = lists:flatten(AppMeta1), - 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 <- AppMeta2]] - end), - {ok, rcl_state:available_apps(State, AppMeta2)}; - _ -> - ?RCL_ERROR(Errors) - end. - -app_name({error, _}) -> - undefined; -app_name(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 - [] -> - {error, {invalid_override, AppName, FileName}}; - Error = {error, _} -> - Error; - App -> - rcl_app_info:link(App, true) - end || {AppName, FileName} <- Overrides] ++ AppMetas1. - get_lib_dirs(State) -> LibDirs0 = rcl_state:lib_dirs(State), case rcl_state:get(State, disable_default_libs, false) of @@ -140,121 +91,3 @@ add_system_lib_dir(State, LibDirs) -> LibDirs end end. - --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()], - file:name()) -> - [rcl_app_info:t() | {error, Reason::term()}] | - rcl_app_info:t() | {error, Reason::term()}. -discover_dir(IgnoreDirs, File) -> - case (not lists:member(File, IgnoreDirs)) - andalso filelib:is_dir(File) of - true -> - case file:list_dir(File) of - {error, Reason} -> - {error, {accessing, File, Reason}}; - {ok, List} -> - ec_plists:map(fun(LibDir) -> - discover_dir(IgnoreDirs, LibDir) - end, [filename:join([File, Dir]) || Dir <- List]) - end; - false -> - is_valid_otp_app(File) - end. - --spec is_valid_otp_app(file:name()) -> rcl_app_info:t() | {error, Reason::term()} | []. -is_valid_otp_app(File) -> - %% Is this an ebin dir? - EbinDir = filename:dirname(File), - case filename:basename(EbinDir) of - "ebin" -> - case lists:suffix(".app", File) of - true -> - has_at_least_one_beam(EbinDir, File); - false -> - [] - end; - _ -> - [] - end. - --spec has_at_least_one_beam(file:name(), file:filename()) -> - 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()) -> - 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()) -> - 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} -> - 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. -- cgit v1.2.3 From a7e6f77f4450cb5bef8c7da594449ff053123876 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 31 Jan 2013 15:25:16 -0800 Subject: fix bugs in constraint parsing for rcl_release --- src/rcl_release.erl | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/rcl_release.erl b/src/rcl_release.erl index 560a555..8cd3033 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -72,7 +72,7 @@ {app_name(), app_vsn(), app_type() | incl_apps()} | {app_name(), app_vsn(), app_type(), incl_apps()}. --type application_constraint() :: rcl_depsolver:constraint() | string() | binary(). +-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}. @@ -189,7 +189,7 @@ 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}) -> +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", @@ -209,10 +209,10 @@ realize_erts(Rel) -> 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]), + [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, @@ -308,7 +308,7 @@ parse_goal0(Constraint0, {ok, Release}) -> parse_goal0(_, E = {error, _}) -> E; parse_goal0(Constraint, _) -> - ?RCL_ERROR({invalid_constraint, Constraint}). + ?RCL_ERROR({invalid_constraint, 1, Constraint}). parse_goal1(Release = #release_t{annotations=Annots, goals=Goals}, Constraint, NewAnnots) -> @@ -331,25 +331,45 @@ parse_constraint(Constraint0) {ok, Constraint1} -> {ok, Constraint1} end; -parse_constraint(Constraint) - when erlang:is_tuple(Constraint); - erlang:is_atom(Constraint) -> - case rcl_depsolver:is_valid_constraint(Constraint) of +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, Constraint}); + ?RCL_ERROR({invalid_constraint, 2, Constraint0}); true -> - {ok, Constraint} + {ok, Constraint1} end; parse_constraint(Constraint) -> - ?RCL_ERROR({invalid_constraint, Constraint}). + ?RCL_ERROR({invalid_constraint, 3, Constraint}). --spec get_app_name(rcl_depsolver: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, 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. -- cgit v1.2.3 From 367d9fdf1d577dd67990cdd4ad4480ba1130bc92 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 31 Jan 2013 15:25:59 -0800 Subject: fix release processing bugs in rcl_prv_release --- src/rcl_prv_release.erl | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl index a98048f..4574a0e 100644 --- a/src/rcl_prv_release.erl +++ b/src/rcl_prv_release.erl @@ -101,36 +101,25 @@ find_default_release(State, DepGraph) -> end. resolve_default_release(State0, DepGraph) -> - %% Here we will just get the lastest version and run that. + %% 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 - All = [{{RelName, RelVsn}, _} | _] -> + [{{RelName, RelVsn}, _} | _] -> State1 = rcl_state:default_release(State0, RelName, RelVsn), - lists:foldl(fun({{RN, RV}, _}, {ok, State2}) -> - solve_release(State2, - DepGraph, RN, RV); - (_, E) -> - E - end, {ok, State1}, All); + 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:releases(State0)), SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases, PossibleRelName =:= RelName], case lists:sort(fun release_sort/2, SpecificReleases) of - All = [{{RelName, RelVsn}, _} | _] -> + [{{RelName, RelVsn}, _} | _] -> State1 = rcl_state:default_release(State0, RelName, RelVsn), - lists:foldl(fun({RN, RV}, {ok, State2}) -> - solve_release(State2, - DepGraph, RN, RV); - (_, E) -> - E - end, {ok, State1}, All); + solve_release(State1, DepGraph, RelName, RelVsn); [] -> ?RCL_ERROR({no_releases_for, RelName}) end. @@ -150,7 +139,11 @@ release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) -> 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 + io:format("Solving ~p ~p", [RelName, RelVsn]), Release = rcl_state:get_release(State0, RelName, RelVsn), Goals = rcl_release:goals(Release), case Goals of @@ -176,7 +169,7 @@ set_resolved(State, Release0, Pkgs) -> "Resolved ~p-~s~n", [rcl_release:name(Release1), rcl_release:vsn(Release1)]), - rcl_log:info(rcl_state:log(State), + rcl_log:debug(rcl_state:log(State), fun() -> rcl_release:format(1, Release1) end), -- cgit v1.2.3 From 8b43038c86a2b4d7329434dab85dd5aac17fc9fe Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 31 Jan 2013 15:26:30 -0800 Subject: support release discovery in relcool along with app discovery --- src/rcl_prv_discover.erl | 17 +++-- src/rcl_rel_discovery.erl | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/rcl_release.erl | 11 ++- test/rclt_release_SUITE.erl | 74 ++++++++++++++++++-- 4 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 src/rcl_rel_discovery.erl diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index 1ffbece..c5a625d 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -41,11 +41,20 @@ init(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) -> - LibDirs = get_lib_dirs(State), - case rcl_app_discovery:do(State, LibDirs) of +do(State0) -> + LibDirs = get_lib_dirs(State0), + case rcl_app_discovery:do(State0, LibDirs) of {ok, AppMeta} -> - {ok, rcl_state:available_apps(State, AppMeta)}; + case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of + {ok, Releases} -> + State1 = rcl_state:available_apps(State0, AppMeta), + State3 = lists:foldl(fun(Rel, State2) -> + rcl_state:add_release(State2, Rel) + end, State1, Releases), + {ok, State3}; + Error -> + Error + end; Error -> Error end. diff --git a/src/rcl_rel_discovery.erl b/src/rcl_rel_discovery.erl new file mode 100644 index 0000000..0fbcf23 --- /dev/null +++ b/src/rcl_rel_discovery.erl @@ -0,0 +1,166 @@ +%% -*- 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(ec_plists:map(fun(LibDir) -> + discover_dir([], LibDir, AppMeta) + end, LibDirs)), + Errors = [case El of + {error, Ret} -> Ret; + _ -> El + end + || El <- ReleaseMeta0, + case El of + {error, _} -> + true; + _ -> + false + end], + + case Errors of + [] -> + ReleaseMeta1 = lists:flatten(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()], file:name(), [rcl_app_info:t()]) -> + [rcl_release:t() | {error, Reason::term()}] + | rcl_release:t() + | {error, Reason::term()}. +discover_dir(IgnoreDirs, File, AppMeta) -> + case (not lists:member(File, IgnoreDirs)) + andalso filelib:is_dir(File) of + true -> + case file:list_dir(File) of + {error, Reason} -> + {error, {accessing, File, Reason}}; + {ok, List} -> + ec_plists:map(fun(LibDir) -> + discover_dir(IgnoreDirs, LibDir, AppMeta) + end, + [filename:join([File, Dir]) || Dir <- List]) + end; + false -> + is_valid_release(File, AppMeta) + end. + +-spec is_valid_release(file:name(), + [rcl_app_info:t()]) -> + rcl_release:t() + | {error, Reason::term()} + | []. +is_valid_release(File, AppMeta) -> + case lists:suffix(".rel", File) of + true -> + resolve_release(File, AppMeta); + false -> + [] + end. + +resolve_release(RelFile, AppMeta) -> + case file:consult(RelFile) of + {ok, [{release, {RelName, RelVsn}, + {erts, ErtsVsn}, + Apps}]} -> + build_release(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), + ErtsVsn), + resolve_apps(Apps, AppMeta, Release, []). + +resolve_apps([], _AppMeta, Release, Acc) -> + rcl_release:application_details(Release, Acc); +resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> + AppName = erlang:element(1, AppInfo), + AppVsn = 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:name(App), + NAppVsn = rcl_app:version(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 index 8cd3033..ea20f1f 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -32,6 +32,7 @@ realize/3, applications/1, application_details/1, + application_details/2, realized/1, metadata/1, format/1, @@ -134,12 +135,18 @@ applications(#release_t{applications=Apps}) -> Apps. %% @doc this gives the rcl_app_info objects representing the applications in -%% this release. These can only be populated by the 'realize' call in this -%% module. +%% 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. diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl index 95aa385..1104303 100644 --- a/test/rclt_release_SUITE.erl +++ b/test/rclt_release_SUITE.erl @@ -32,7 +32,8 @@ overlay_release/1, make_goalless_release/1, make_depfree_release/1, - make_invalid_config_release/1]). + make_invalid_config_release/1, + make_relup_release/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -60,7 +61,7 @@ all() -> [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_invalid_config_release, make_relup_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -178,7 +179,7 @@ make_overridden_release(Config) -> || _ <- lists:seq(1, 100)]], OverrideApp = create_random_name("override_app"), OverrideVsn = create_random_vsn(), - OverrideAppDir = filename:join(OverrideDir1, OverrideApp), + 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], []), @@ -268,7 +269,8 @@ make_rerun_overridden_release(Config) -> || _ <- lists:seq(1, 100)]], OverrideApp = create_random_name("override_app"), OverrideVsn = create_random_vsn(), - OverrideAppDir = filename:join(OverrideDir1, OverrideApp), + 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], []), @@ -500,11 +502,73 @@ make_depfree_release(Config) -> ?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], []), + 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], []), + 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, "0.0.1"}, + {goal_app_2, "0.0.1"}]}, + {release, {foo, "0.0.2"}, + [{goal_app_1, "0.0.2"}, + {goal_app_2, "0.0.2"}]}, + {release, {foo, "0.0.3"}, + [{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(foo, "0.0.3", [], [LibDir1], 2, + OutputDir, ConfigFile), + + %% we should have one 'resolved' release and three discovered 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)), + ?assert(rcl_release:realized(Release)), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.2"}, + rcl_state:releases(State)))), + ?assert(not rcl_release:realized(ec_dictionary:get({foo, "0.0.1"}, + rcl_state:releases(State)))), + ?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)). + %%%=================================================================== %%% Helper Functions %%%=================================================================== create_app(Dir, Name, Vsn, Deps, LibDeps) -> - AppDir = filename:join([Dir, Name]), + 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, -- cgit v1.2.3 From 882d4fbfe65dfc812d3de9d55647f9fb468be4b4 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 31 Jan 2013 15:26:40 -0800 Subject: fix formatting bigs in depsolver --- src/rcl_depsolver.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rcl_depsolver.erl b/src/rcl_depsolver.erl index e74cf3b..0cf8c88 100644 --- a/src/rcl_depsolver.erl +++ b/src/rcl_depsolver.erl @@ -471,12 +471,13 @@ add_constraint(SrcPkg, SrcVsn, PkgsConstraints, PkgConstraint) -> {value, {PkgName, Constraints0}} -> Constraints0 end, - [{PkgName, [{PkgConstraint, {SrcPkg, SrcVsn}} | Constraints1]} | - lists:keydelete(PkgName, 1, PkgsConstraints)]. + [{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()}]. +-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) -- cgit v1.2.3 From 1207514752a0e522893c60eb6434cb4a8117a7f1 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Mon, 4 Feb 2013 09:58:20 -0600 Subject: make all grabs deps if deps dir does not exist --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a7e9bb6..96e14ae 100644 --- a/Makefile +++ b/Makefile @@ -38,16 +38,20 @@ endif .PHONY: all compile doc clean test dialyzer typer shell distclean pdf \ get-deps escript clean-common-test-data rebuild -all: compile escript dialyzer test +all: deps compile escript dialyzer test # ============================================================================= # Rules to build the system # ============================================================================= -get-deps: +deps: $(REBAR) get-deps $(REBAR) compile +update-deps: + $(REBAR) update-deps + $(REBAR) compile + compile: $(REBAR) skip_deps=true compile -- cgit v1.2.3 From 2b20716439dd0f540dbf78cc6eaabf1aa32f8583 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Mon, 4 Feb 2013 18:08:13 -0600 Subject: fix: replace COnfigFile with empty string if undefined --- src/rcl_state.erl | 2 +- src/rcl_util.erl | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/rcl_state.erl b/src/rcl_state.erl index ecf3115..25497f8 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -256,7 +256,7 @@ format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, [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: ", ConfigFile, "\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", diff --git a/src/rcl_util.erl b/src/rcl_util.erl index 06d957d..a879c58 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -25,7 +25,8 @@ to_binary/1, is_error/1, error_reason/1, - indent/1]). + indent/1, + optional_to_string/1]). -define(ONE_LEVEL_INDENT, " "). %%============================================================================ @@ -68,7 +69,11 @@ is_error({error, _}) -> 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 -- cgit v1.2.3 From 2558618906a221ebe3cc4b2582bf59d9d7396de5 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Mon, 4 Feb 2013 20:30:14 -0600 Subject: fix: specifying config file as argument no longer crashes --- src/rcl_cmd_args.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index 973abe3..bfb63b7 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -121,7 +121,7 @@ validate_config("") -> validate_config(Config) -> case filelib:is_regular(Config) of true -> - filename:absname(Config); + {ok, filename:absname(Config)}; false -> ?RCL_ERROR({invalid_config_file, Config}) end. -- cgit v1.2.3 From 26851a2e1c59124dea325e9a38cda25da463839a Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 5 Feb 2013 16:49:41 -0800 Subject: fix config issues with undefined config --- src/rcl_prv_config.erl | 4 ++-- src/rcl_state.erl | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl index 5d51300..60655b1 100644 --- a/src/rcl_prv_config.erl +++ b/src/rcl_prv_config.erl @@ -46,9 +46,9 @@ init(State) -> -spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error(). do(State) -> case rcl_state:config_file(State) of - [] -> + undefined -> search_for_dominating_config(State); - ConfigFile -> + ConfigFile when erlang:is_list(ConfigFile) -> load_config(ConfigFile, State) end. diff --git a/src/rcl_state.erl b/src/rcl_state.erl index ecf3115..149b5ab 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -65,7 +65,7 @@ action :: atom(), output_dir :: file:name(), lib_dirs=[] :: [file:name()], - config_file=[] :: file:filename(), + config_file=[] :: file:filename() | undefined, goals=[] :: [rcl_depsolver:constraint()], providers = [] :: [rcl_provider:t()], available_apps = [] :: [rcl_app_info:t()], @@ -102,7 +102,7 @@ new(PropList, Target) #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), output_dir=proplists:get_value(output_dir, PropList, ""), lib_dirs=proplists:get_value(lib_dirs, PropList, ""), - config_file=proplists:get_value(config, PropList, ""), + config_file=proplists:get_value(config, PropList, undefined), action = Target, goals=proplists:get_value(goals, PropList, []), providers = [], @@ -143,11 +143,11 @@ lib_dirs(#state_t{lib_dirs=LibDir}) -> goals(#state_t{goals=TS}) -> TS. --spec config_file(t()) -> file:filename(). +-spec config_file(t()) -> file:filename() | undefined. config_file(#state_t{config_file=ConfigFiles}) -> ConfigFiles. --spec config_file(t(), file:filename()) -> t(). +-spec config_file(t(), file:filename() | undefined) -> t(). config_file(State, ConfigFiles) -> State#state_t{config_file=ConfigFiles}. -- cgit v1.2.3 From a90291da9eb49e36e17c13afc31517bb636e6426 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 5 Feb 2013 16:49:59 -0800 Subject: fix bug in error reporting by relcool --- src/relcool.erl | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/relcool.erl b/src/relcool.erl index 83886c5..c8c0cde 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -44,12 +44,20 @@ -spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. main(Args) -> OptSpecList = opt_spec_list(), - case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of - {ok, State} -> - run_relcool_process(rcl_state:caller(State, command_line)); - Error={error, _} -> + 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, + case Result of + {error, _} -> report_error(rcl_state:caller(rcl_state:new([], undefined), - command_line), Error) + command_line), + Result); + _ -> + Result end. %% @doc provides an API to run the Relcool process from erlang applications @@ -128,7 +136,7 @@ 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"}, - {config, $c, "config", string, "The path to a config file"}, + {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(). @@ -194,9 +202,12 @@ run_provider(Provider, {ok, State0}) -> [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. -- cgit v1.2.3 From 9c88211a86da5812272df0fd0bd82ace3c6232ed Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 6 Feb 2013 14:21:30 -0800 Subject: fix bug in app version comparison --- src/rcl_rel_discovery.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rcl_rel_discovery.erl b/src/rcl_rel_discovery.erl index 0fbcf23..0f923a0 100644 --- a/src/rcl_rel_discovery.erl +++ b/src/rcl_rel_discovery.erl @@ -144,7 +144,7 @@ resolve_apps([], _AppMeta, Release, Acc) -> rcl_release:application_details(Release, Acc); resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> AppName = erlang:element(1, AppInfo), - AppVsn = erlang:element(2, AppInfo), + AppVsn = ec_semver:parse(erlang:element(2, AppInfo)), case find_app(AppName, AppVsn, AppMeta) of Error = {error, _} -> Error; @@ -154,8 +154,8 @@ resolve_apps([AppInfo | Apps], AppMeta, Release, Acc) -> find_app(AppName, AppVsn, AppMeta) -> case ec_lists:find(fun(App) -> - NAppName = rcl_app:name(App), - NAppVsn = rcl_app:version(App), + NAppName = rcl_app_info:name(App), + NAppVsn = rcl_app_info:vsn(App), AppName == NAppName andalso AppVsn == NAppVsn end, AppMeta) of -- cgit v1.2.3 From 6299d16b6205edfe3eff396ec72ef8566cfabe67 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 5 Feb 2013 16:51:03 -0800 Subject: fix problem in release names from discovered releases --- src/rcl_release.erl | 11 +++++++++-- src/rcl_util.erl | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/rcl_release.erl b/src/rcl_release.erl index ea20f1f..97465d0 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -89,7 +89,7 @@ %%============================================================================ -spec new(atom(), string()) -> t(). new(ReleaseName, ReleaseVsn) -> - #release_t{name=ReleaseName, vsn=ReleaseVsn, + #release_t{name=to_atom(ReleaseName), vsn=ReleaseVsn, annotations=ec_dictionary:new(ec_dict)}. -spec name(t()) -> atom(). @@ -170,7 +170,7 @@ format(Release) -> format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, goals = Goals, applications=Apps}) -> BaseIndent = rcl_util:indent(Indent), - [BaseIndent, "release: ", erlang:atom_to_list(Name), "-", Vsn, "\n", + [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", @@ -380,3 +380,10 @@ parse_version({AppName, Version, Constraint0, Constraint1}) {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_util.erl b/src/rcl_util.erl index 06d957d..9cb20ac 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -23,6 +23,7 @@ -export([mkdir_p/1, to_binary/1, + to_string/1, is_error/1, error_reason/1, indent/1]). @@ -56,6 +57,10 @@ to_binary(String) when erlang:is_list(String) -> erlang:iolist_to_binary(String); to_binary(Bin) when erlang:is_binary(Bin) -> Bin. +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(). -- cgit v1.2.3 From 2436289d373f16648a9768d66a658c42b2a91781 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 5 Feb 2013 16:51:16 -0800 Subject: remove io:format that some how made it in --- src/rcl_prv_release.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl index 4574a0e..eac1f20 100644 --- a/src/rcl_prv_release.erl +++ b/src/rcl_prv_release.erl @@ -143,7 +143,6 @@ solve_release(State0, DepGraph, RelName, RelVsn) -> "Solving Release ~p-~s~n", [RelName, RelVsn]), try - io:format("Solving ~p ~p", [RelName, RelVsn]), Release = rcl_state:get_release(State0, RelName, RelVsn), Goals = rcl_release:goals(Release), case Goals of -- cgit v1.2.3 From 35040be995d9b2f1b3b601e69a998156145e1b35 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 5 Feb 2013 17:07:21 -0800 Subject: fix bug in on disk overriding the default release --- src/rcl_prv_discover.erl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl index c5a625d..2f88354 100644 --- a/src/rcl_prv_discover.erl +++ b/src/rcl_prv_discover.erl @@ -48,9 +48,8 @@ do(State0) -> case rcl_rel_discovery:do(State0, LibDirs, AppMeta) of {ok, Releases} -> State1 = rcl_state:available_apps(State0, AppMeta), - State3 = lists:foldl(fun(Rel, State2) -> - rcl_state:add_release(State2, Rel) - end, State1, Releases), + State3 = lists:foldl(fun add_if_not_found/2, + State1, Releases), {ok, State3}; Error -> Error @@ -68,6 +67,18 @@ format_error(_) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== +%% @doc only add the release if its not documented in the system +add_if_not_found(Rel, State) -> + RelName = rcl_release:name(Rel), + RelVsn = rcl_release:vsn(Rel), + try + rcl_state:get_release(State, RelName, RelVsn), + State + catch + throw:not_found -> + rcl_state:add_release(State, Rel) + end. + get_lib_dirs(State) -> LibDirs0 = rcl_state:lib_dirs(State), case rcl_state:get(State, disable_default_libs, false) of -- cgit v1.2.3