diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | rebar.config | 2 | ||||
-rw-r--r-- | src/rcl_cmd_args.erl | 15 | ||||
-rw-r--r-- | src/rcl_prv_assembler.erl | 33 | ||||
-rw-r--r-- | src/rcl_prv_config.erl | 50 | ||||
-rw-r--r-- | src/rcl_state.erl | 20 | ||||
-rw-r--r-- | src/relcool.erl | 4 | ||||
-rw-r--r-- | test/rclt_command_SUITE.erl | 15 | ||||
-rw-r--r-- | test/rclt_release_SUITE.erl | 45 |
9 files changed, 145 insertions, 41 deletions
@@ -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. 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}. 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(). 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), 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 7397e6f..bd26a47 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -31,10 +31,13 @@ overrides/2, goals/1, config_files/1, + config_files/2, providers/1, 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 +60,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 +94,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 +105,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). @@ -135,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. @@ -147,6 +157,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 0d75c62..c6747ca 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(). @@ -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) 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)). %%%=================================================================== |