diff options
-rw-r--r-- | src/relx.app.src | 2 | ||||
-rw-r--r-- | src/relx.erl | 12 | ||||
-rw-r--r-- | src/rlx_cmd_args.erl | 43 | ||||
-rw-r--r-- | src/rlx_goal.erl | 16 | ||||
-rw-r--r-- | src/rlx_prv_assembler.erl | 200 | ||||
-rw-r--r-- | src/rlx_prv_release.erl | 17 | ||||
-rw-r--r-- | src/rlx_rel_discovery.erl | 2 | ||||
-rw-r--r-- | src/rlx_state.erl | 28 | ||||
-rw-r--r-- | src/rlx_util.erl | 16 | ||||
-rw-r--r-- | test/rlx_discover_SUITE.erl | 2 | ||||
-rw-r--r-- | test/rlx_release_SUITE.erl | 14 |
11 files changed, 258 insertions, 94 deletions
diff --git a/src/relx.app.src b/src/relx.app.src index 3f655cf..c52958b 100644 --- a/src/relx.app.src +++ b/src/relx.app.src @@ -20,7 +20,7 @@ {application, relx, [{description, "Release assembler for Erlang/OTP Releases"}, - {vsn, "0.0.5"}, + {vsn, "semver"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, getopt, erlware_commons]}]}. diff --git a/src/relx.erl b/src/relx.erl index 30ee77f..7419352 100644 --- a/src/relx.erl +++ b/src/relx.erl @@ -156,12 +156,12 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Con -spec do(proplists:proplist(), [string()]) -> ok | error() | {ok, rlx_state:t()}. do(Opts, NonOpts) -> - case rlx_cmd_args:args2state(Opts, NonOpts) of - {ok, State} -> - run_relx_process(State); - Error={error, _} -> - Error - end. + case rlx_cmd_args:args2state(Opts, NonOpts) of + {ok, State} -> + run_relx_process(State); + Error={error, _} -> + Error + end. -spec opt_spec_list() -> [getopt:option_spec()]. opt_spec_list() -> diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl index 1a8e7f2..4fbadba 100644 --- a/src/rlx_cmd_args.erl +++ b/src/rlx_cmd_args.erl @@ -32,24 +32,21 @@ -spec args2state([getopt:option()], [string()]) -> {ok, {rlx_state:t(), [string()]}} | relx:error(). -args2state(Opts, Target) - when erlang:length(Target) == 0; erlang:length(Target) == 1 -> +args2state(Opts, Targets) -> RelName = rlx_util:to_atom(proplists:get_value(relname, Opts, undefined)), RelVsn = proplists:get_value(relvsn, Opts, undefined), - case convert_target(Target) of - {ok, AtomizedTarget} -> + case convert_targets(Targets) of + {ok, AtomizedTargets} -> case create_log(Opts, [{relname, RelName}, {relvsn, RelVsn}]) of Error = {error, _} -> Error; {ok, CommandLineConfig} -> - handle_config(Opts, AtomizedTarget, CommandLineConfig) + handle_config(Opts, AtomizedTargets, CommandLineConfig) end; Error -> Error - end; -args2state(_Opts, Targets) -> - ?RLX_ERROR({invalid_targets, Targets}). + end. -spec format_error(Reason::term()) -> iolist(). format_error({invalid_targets, Targets}) -> @@ -88,25 +85,33 @@ format_error({invalid_target, Target}) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== --spec handle_config([getopt:option()], atom(), proplists:proplist()) -> +-spec handle_config([getopt:option()], [atom()], proplists:proplist()) -> {ok, {rlx_state:t(), [string()]}} | relx:error(). -handle_config(Opts, Target, CommandLineConfig) -> +handle_config(Opts, Targets, CommandLineConfig) -> case validate_config(proplists:get_value(config, Opts, [])) of Error = {error, _} -> Error; {ok, Config} -> - {ok, rlx_state:new([{config, Config} | CommandLineConfig], Target)} + {ok, rlx_state:new([{config, Config} | CommandLineConfig], Targets)} end. --spec convert_target([string()]) -> {ok, release | relup} | relx:error(). -convert_target([]) -> - {ok, release}; -convert_target(["release"]) -> - {ok, release}; -convert_target(["relup"]) -> - {ok, relup}; -convert_target(Target) -> +-spec convert_targets([string()]) -> {ok, release | relup} | relx:error(). +convert_targets(Targets) -> + convert_targets(Targets, []). + +-spec convert_targets([string()], [string()]) -> {ok, release | relup} | relx:error(). +convert_targets([], []) -> + {ok, [release]}; +convert_targets([], Acc) -> + {ok, Acc}; +convert_targets(["release" | T], Acc) -> + convert_targets(T, [release | Acc]); +convert_targets(["relup" | T], Acc) -> + convert_targets(T, [relup | Acc]); +convert_targets(["tar" | T], Acc) -> + convert_targets(T, [tar | Acc]); +convert_targets([Target | _T], _Acc) -> ?RLX_ERROR({invalid_target, Target}). -spec validate_config(file:filename() | undefined) -> diff --git a/src/rlx_goal.erl b/src/rlx_goal.erl index 7226525..138a197 100644 --- a/src/rlx_goal.erl +++ b/src/rlx_goal.erl @@ -5,7 +5,7 @@ -compile(export_all). -spec file(file:name()) -> any(). -file(Filename) -> {ok, Bin} = file:read_file(Filename), parse(Bin). +file(Filename) -> case file:read_file(Filename) of {ok,Bin} -> parse(Bin); Err -> Err end. -spec parse(binary() | list()) -> any(). parse(List) when is_list(List) -> parse(list_to_binary(List)); @@ -18,7 +18,7 @@ parse(Input) when is_binary(Input) -> release_memo(), Result. 'constraint'(Input, Index) -> - p(Input, Index, 'constraint', fun(I,D) -> (p_choose([p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'between_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_string(<<",">>), p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'constraint_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), p_not(p_anything())])]))(I,D) end, fun(Node, _Idx) -> + p(Input, Index, 'constraint', fun(I,D) -> (p_choose([p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'between_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_string(<<",">>), p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), fun 'constraint_op'/2, p_optional(fun 'ws'/2), fun 'version'/2, p_optional(fun 'ws'/2), p_not(p_anything())]), p_seq([p_optional(fun 'ws'/2), fun 'app_name'/2, p_optional(fun 'ws'/2), p_not(p_anything())])]))(I,D) end, fun(Node, _Idx) -> case Node of [_,AppName,_, _] -> {ok, AppName}; @@ -39,30 +39,30 @@ parse(Input) when is_binary(Input) -> end). 'ws'(Input, Index) -> - p(Input, Index, 'ws', fun(I,D) -> (p_charclass(<<"[\s\t\n\s\r]">>))(I,D) end, fun(Node, Idx) -> transform('ws', Node, Idx) end). + p(Input, Index, 'ws', fun(I,D) -> (p_charclass(<<"[\s\t\n\s\r]">>))(I,D) end, fun(Node, Idx) ->transform('ws', Node, Idx) end). 'app_name'(Input, Index) -> - p(Input, Index, 'app_name', fun(I,D) -> (p_one_or_more(p_charclass(<<"[a-zA-Z0-9_]">>)))(I,D) end, fun(Node, _Idx) -> erlang:list_to_atom(erlang:binary_to_list(erlang:iolist_to_binary(Node))) end). + p(Input, Index, 'app_name', fun(I,D) -> (p_one_or_more(p_charclass(<<"[a-zA-Z0-9_]">>)))(I,D) end, fun(Node, _Idx) -> erlang:list_to_atom(erlang:binary_to_list(erlang:iolist_to_binary(Node))) end). 'between_op'(Input, Index) -> - p(Input, Index, 'between_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"btwn">>), p_string(<<"between">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of + p(Input, Index, 'between_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"btwn">>), p_string(<<"between">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); _ -> Node end end). 'constraint_op'(Input, Index) -> - p(Input, Index, 'constraint_op', fun(I,D) -> (p_choose([p_string(<<"=">>), p_string(<<"-">>), p_string(<<"<=">>), p_string(<<"<">>), p_string(<<"~>">>), p_string(<<">=">>), p_string(<<">">>), fun 'word_constraint_op'/2, p_string(<<":">>)]))(I,D) end, fun(Node, Idx) -> transform('constraint_op', Node, Idx) end). + p(Input, Index, 'constraint_op', fun(I,D) -> (p_choose([p_string(<<"=">>), p_string(<<"-">>), p_string(<<"<=">>), p_string(<<"<">>), p_string(<<"~>">>), p_string(<<">=">>), p_string(<<">">>), fun 'word_constraint_op'/2, p_string(<<":">>)]))(I,D) end, fun(Node, Idx) ->transform('constraint_op', Node, Idx) end). 'word_constraint_op'(Input, Index) -> - p(Input, Index, 'word_constraint_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"gte">>), p_string(<<"lte">>), p_string(<<"gt">>), p_string(<<"lt">>), p_string(<<"pes">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of + p(Input, Index, 'word_constraint_op', fun(I,D) -> (p_seq([p_string(<<":">>), p_optional(fun 'ws'/2), p_choose([p_string(<<"gte">>), p_string(<<"lte">>), p_string(<<"gt">>), p_string(<<"lt">>), p_string(<<"pes">>)]), p_optional(fun 'ws'/2), p_string(<<":">>)]))(I,D) end, fun(Node, _Idx) -> case Node of [C,_,Op,_,C] -> erlang:iolist_to_binary([C,Op,C]); _ -> Node end end). 'version'(Input, Index) -> - p(Input, Index, 'version', fun(I,D) -> (p_one_or_more(p_charclass(<<"[0-9a-zA-Z-+.]">>)))(I,D) end, fun(Node, Idx) -> transform('version', Node, Idx) end). + p(Input, Index, 'version', fun(I,D) -> (p_one_or_more(p_charclass(<<"[0-9a-zA-Z-+.]">>)))(I,D) end, fun(Node, Idx) ->transform('version', Node, Idx) end). transform(_,Node,_Index) -> Node. diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl index 52894f2..db5d147 100644 --- a/src/rlx_prv_assembler.erl +++ b/src/rlx_prv_assembler.erl @@ -48,7 +48,7 @@ do(State) -> ok -> case rlx_release:realized(Release) of true -> - copy_app_directories_to_output(State, Release, OutputDir); + run_actions(State, Release, OutputDir); false -> ?RLX_ERROR({unresolved_release, RelName, RelVsn}) end; @@ -56,6 +56,34 @@ do(State) -> Error end. +do(release, State, Release, OutputDir) -> + copy_app_directories_to_output(State, Release, OutputDir); +do(relup, State, Release, _OutputDir) -> + RelName = rlx_release:name(Release), + RelVsn = rlx_release:vsn(Release), + Release0 = rlx_state:get_realized_release(State, RelName, RelVsn), + make_relup(State, Release0); +do(tar, State, Release, OutputDir) -> + make_tar(State, Release, OutputDir). + +run_actions(State, Release, OutputDir) -> + run_actions(State, Release, OutputDir, rlx_state:actions(State), [release, relup, tar]). + +run_actions(State, _Release, _OutputDir, _Actions, []) -> + {ok, State}; +run_actions(State, Release, OutputDir, Actions, [H | T]) -> + case lists:member(H, Actions) of + true -> + case do(H, State, Release, OutputDir) of + {ok, NewState} -> + run_actions(NewState, Release, OutputDir, Actions, T); + Error -> + Error + end; + false -> + run_actions(State, Release, OutputDir, Actions, T) + 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]); @@ -87,7 +115,7 @@ format_error({relup_generation_warning, Module, Warnings}) -> ["Warnings generating relup \s", rlx_util:indent(1), Module:format_warning(Warnings)]; format_error({relup_script_generation_error, - {relupcript_generation_error, systools_relup, + {relup_script_generation_error, systools_relup, {missing_sasl, _}}}) -> "Unfortunately, due to requirements in systools, you need to have the sasl application " "in both the current release and the release to upgrade from."; @@ -97,7 +125,16 @@ format_error({relup_script_generation_error, Module, 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, rlx_util:indent(1), - file:format_error(Reason)]). + file:format_error(Reason)]); +format_error({tar_unknown_generation_error, Module, Vsn}) -> + io_lib:format("Tarball generation error of ~s ~s", + [Module, Vsn]); +format_error({tar_generation_warn, Module, Warnings}) -> + io_lib:format("Tarball generation warnings for ~p : ~p", + [Module, Warnings]); +format_error({tar_generation_error, Module, Errors}) -> + io_lib:format("Tarball generation error for ~p reason ~p", + [Module, Errors]). %%%=================================================================== %%% Internal Functions @@ -208,7 +245,7 @@ copy_dir(AppDir, TargetDir, SubDir) -> end. create_release_info(State0, Release0, OutputDir) -> - RelName = erlang:atom_to_list(rlx_release:name(Release0)), + RelName = atom_to_list(rlx_release:name(Release0)), ReleaseDir = release_output_dir(State0, Release0), ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), @@ -330,9 +367,13 @@ include_erts(State, Release, OutputDir, RelDir) -> ok = ec_file:copy(filename:join([Prefix, "bin", "start_clean.boot"]), filename:join([OutputDir, "bin", "start_clean.boot"])), NodeToolFile = nodetool_contents(), + InstallUpgradeFile = install_upgrade_escript_contents(), NodeTool = filename:join([LocalErts, "bin", "nodetool"]), + InstallUpgrade = filename:join([LocalErts, "bin", "install_upgrade.escript"]), ok = file:write_file(NodeTool, NodeToolFile), - ok = file:change_mode(NodeTool, 8#755); + ok = file:write_file(InstallUpgrade, InstallUpgradeFile), + ok = file:change_mode(NodeTool, 8#755), + ok = file:change_mode(InstallUpgrade, 8#755); false -> ok end, @@ -358,13 +399,13 @@ make_boot_script(State, Release, OutputDir, RelDir) -> ok -> rlx_log:error(rlx_state:log(State), "release successfully created!"), - make_relup(State, Release); + {ok, State}; error -> ?RLX_ERROR({release_script_generation_error, ReleaseFile}); {ok, _, []} -> rlx_log:error(rlx_state:log(State), "release successfully created!"), - make_relup(State, Release); + {ok, State}; {ok,Module,Warnings} -> ?RLX_ERROR({release_script_generation_warn, Module, Warnings}); {error,Module,Error} -> @@ -385,30 +426,72 @@ make_script(Options, RunFun) -> end. make_relup(State, Release) -> - case rlx_state:action(State) of - relup -> - UpFrom = - case rlx_state:upfrom(State) of - undefined -> - get_last_release(State, Release); - Vsn -> - get_up_release(State, Release, Vsn) - end, - case UpFrom of - undefined -> - ?RLX_ERROR(no_upfrom_release_found); - _ -> - make_upfrom_script(State, Release, UpFrom) - end; + UpFrom = + case rlx_state:upfrom(State) of + undefined -> + get_last_release(State, Release); + Vsn -> + get_up_release(State, Release, Vsn) + end, + case UpFrom of + undefined -> + ?RLX_ERROR(no_upfrom_release_found); _ -> - {ok, State} + make_upfrom_script(State, Release, UpFrom) + end. + +make_tar(State, Release, OutputDir) -> + Name = atom_to_list(rlx_release:name(Release)), + Vsn = rlx_release:vsn(Release), + Prefix = code:root_dir(), + ErtsVersion = rlx_release:erts(Release), + ErtsDir = filename:join([Prefix]), + case systools:make_tar(filename:join([OutputDir, "releases", Vsn, Name]), + [{path, [filename:join([OutputDir, "lib", "*", "ebin"])]}, + {erts, ErtsDir}, + {outdir, OutputDir}]) of + ok -> + TempDir = filename:join(OutputDir, integer_to_list(erlang:phash2(make_ref()))), + try + update_tar(State, TempDir, OutputDir, Name, Vsn, ErtsVersion) + catch + E:R -> + file:del_dir(TempDir), + ?RLX_ERROR({tar_generation_error, E, R}) + end; + {ok, Module, Warnings} -> + ?RLX_ERROR({tar_generation_warn, Module, Warnings}); + error -> + ?RLX_ERROR({tar_unknown_generation_error, Name, Vsn}); + {error, Module, Errors} -> + ?RLX_ERROR({tar_generation_error, Module, Errors}) end. +update_tar(State, TempDir, OutputDir, Name, Vsn, ErtsVersion) -> + TarFile = filename:join(OutputDir, Name++"-"++Vsn++".tar.gz"), + file:rename(filename:join(OutputDir, Name++".tar.gz"), TarFile), + erl_tar:extract(TarFile, [{cwd, TempDir}, compressed]), + ok = erl_tar:create(TarFile, + [{"erts-"++ErtsVersion, filename:join(TempDir, "erts-"++ErtsVersion)}, + {filename:join(["erts-"++ErtsVersion, "bin", "nodetool"]), + hd(nodetool_contents())}, + {filename:join(["erts-"++ErtsVersion, "bin", "install_upgrade.escript"]), + hd(install_upgrade_escript_contents())}, + {"lib", filename:join(TempDir, "lib")}, + {"releases", filename:join(TempDir, "releases")}, + {filename:join(["releases", Vsn, "vm.args"]), + filename:join([OutputDir, "releases", Vsn, "vm.args"])}, + {"bin", filename:join([OutputDir, "bin"])}], [compressed]), + rlx_log:info(rlx_state:log(State), + "tarball ~s successfully created!~n", [TarFile]), + rlx_util:delete_dir(TempDir), + {ok, State}. + make_upfrom_script(State, Release, UpFrom) -> OutputDir = rlx_state:output_dir(State), Options = [{outdir, OutputDir}, {path, get_code_paths(Release, OutputDir) ++ - get_code_paths(UpFrom, OutputDir)}, + get_code_paths(UpFrom, OutputDir)}, silent], CurrentRel = strip_rel(rlx_release:relfile(Release)), UpFromRel = strip_rel(rlx_release:relfile(UpFrom)), @@ -434,13 +517,12 @@ make_upfrom_script(State, Release, UpFrom) -> {ok,_, Module,Warnings} -> ?RLX_ERROR({relup_script_generation_warn, Module, Warnings}); {error,Module,Errors} -> - ?RLX_ERROR({relupcript_generation_error, Module, Errors}) + ?RLX_ERROR({relup_script_generation_error, Module, Errors}) end. write_relup_file(State, Release, Relup) -> OutDir = release_output_dir(State, Release), - RelName = rlx_util:to_string(rlx_release:name(Release)), - RelupFile = filename:join(OutDir, RelName ++ ".relup"), + RelupFile = filename:join(OutDir, "relup"), ok = ec_file:write_term(RelupFile, Relup). strip_rel(Name) -> @@ -485,7 +567,7 @@ release_output_dir(State, Release) -> OutputDir = rlx_state:output_dir(State), filename:join([OutputDir, "releases", - rlx_release:canonical_name(Release)]). + rlx_release:vsn(Release)]). %% @doc Generates the correct set of code paths for the system. -spec get_code_paths(rlx_release:t(), file:name()) -> [file:name()]. @@ -522,7 +604,7 @@ RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` REL_NAME=">>, RelName, <<" REL_VSN=">>, RelVsn, <<" ERTS_VSN=">>, ErtsVsn, <<" -REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_VSN ERL_OPTS=">>, ErlOpts, <<" find_erts_dir() { @@ -568,9 +650,9 @@ RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` REL_NAME=">>, RelName, <<" REL_VSN=">>, RelVsn, <<" ERTS_VSN=">>, ErtsVsn, <<" -REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_VSN ERL_OPTS=">>, ErlOpts, <<" -PIPE_DIR=/tmp/$REL_DIR/ +PIPE_DIR=/tmp/erl_pipes/">>, RelName, <<"/ find_erts_dir() { local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN @@ -789,7 +871,7 @@ case \"$1\" in node_name=`echo $NAME_ARG | awk '{print $2}'` erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` - $ERTS_DIR/bin/escript $SCRIPT_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 + $ERTS_DIR/bin/escript $ERTS_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 ;; console|console_clean|console_boot) @@ -797,7 +879,7 @@ case \"$1\" in # however, for debugging, sometimes start_clean.boot is useful. # For e.g. 'setup', one may even want to name another boot script. case \"$1\" in - console) BOOTFILE=$REL_NAME ;; + console) [[ -f $REL_DIR/$REL_NAME ]] && BOOTFILE=$REL_NAME || BOOTFILE=start ;; console_clean) BOOTFILE=start_clean ;; console_boot) shift @@ -831,7 +913,7 @@ case \"$1\" in # start up the release in the foreground for use by runit # or other supervision services - BOOTFILE=$REL_NAME + [[ -f $REL_DIR/$REL_NAME ]] && BOOTFILE=$REL_NAME || BOOTFILE=start FOREGROUNDOPTIONS=\"-noinput +Bd\" # Setup beam-required vars @@ -860,6 +942,56 @@ esac exit 0">>]. +install_upgrade_escript_contents() -> + [<<"#!/usr/bin/env escript +%%! -noshell -noinput +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et + +-define(TIMEOUT, 60000). +-define(INFO(Fmt,Args), io:format(Fmt,Args)). + +main([NodeName, Cookie, ReleasePackage]) ->TargetNode = start_distribution(NodeName, Cookie), + {ok, Cwd} = file:get_cwd(), + ok = rpc:call(TargetNode, file, set_cwd, + [Cwd], ?TIMEOUT), + case rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], ?TIMEOUT) of + {ok, Vsn} -> + ?INFO(\"Unpacked Release ~p~n\", [Vsn]), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + check_install_release, [Vsn], ?TIMEOUT), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + install_release, [Vsn], ?TIMEOUT), + ?INFO(\"Installed Release ~p~n\", [Vsn]), + ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), + ?INFO(\"Made Release ~p Permanent~n\", [Vsn]); + {error, {existing_release, Vsn}} -> + ?INFO(\"Release ~s already installed~n\", [Vsn]) + end; +main(_) -> + init:stop(1). + +start_distribution(NodeName, Cookie) -> + MyNode = make_script_node(NodeName), + {ok, _Pid} = net_kernel:start([MyNode, longnames]), + erlang:set_cookie(node(), list_to_atom(Cookie)), + TargetNode = list_to_atom(NodeName), + case {net_kernel:connect_node(TargetNode), + net_adm:ping(TargetNode)} of + {true, pong} -> + ok; + {_, pang} -> + io:format(\"Node ~p not responding to pings.\n\", [TargetNode]), + init:stop(1) + end, + TargetNode. + +make_script_node(Node) -> + [Name, Host] = string:tokens(Node, \"@\"), + list_to_atom(lists:concat([Name, \"_upgrader_\", os:getpid(), \"@\", Host])). +">>]. + nodetool_contents() -> [<<"%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et diff --git a/src/rlx_prv_release.erl b/src/rlx_prv_release.erl index ce14f4e..cf13953 100644 --- a/src/rlx_prv_release.erl +++ b/src/rlx_prv_release.erl @@ -142,7 +142,13 @@ solve_release(State0, DepGraph, RelName, RelVsn) -> "Solving Release ~p-~s~n", [RelName, RelVsn]), try - Release = rlx_state:get_configured_release(State0, RelName, RelVsn), + Release = + case get_realized_release(State0, RelName, RelVsn) of + undefined -> + rlx_state:get_configured_release(State0, RelName, RelVsn); + {ok, Release0} -> + rlx_release:relfile(rlx_state:get_configured_release(State0, RelName, RelVsn), rlx_release:relfile(Release0)) + end, Goals = rlx_release:goals(Release), case Goals of [] -> @@ -176,6 +182,15 @@ set_resolved(State, Release0, Pkgs) -> ?RLX_ERROR({release_error, E}) end. +get_realized_release(State, RelName, RelVsn) -> + try + Release = rlx_state:get_realized_release(State, RelName, RelVsn), + {ok, Release} + catch + throw:not_found -> + undefined + end. + %%%=================================================================== %%% Test Functions %%%=================================================================== diff --git a/src/rlx_rel_discovery.erl b/src/rlx_rel_discovery.erl index c1e74fb..3cdca3e 100644 --- a/src/rlx_rel_discovery.erl +++ b/src/rlx_rel_discovery.erl @@ -86,6 +86,8 @@ resolve_rel_metadata(State, LibDirs, AppMeta) -> end. -spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({_Module, {could_not_find, {ReleaseName, {Version, _}}}}) -> + io_lib:format("could not find app ~p ~p", [ReleaseName, Version]); format_detail({accessing, File, eaccess}) -> io_lib:format("permission denied accessing file ~s", [File]); format_detail({accessing, File, Type}) -> diff --git a/src/rlx_state.erl b/src/rlx_state.erl index f5f363e..d03631d 100644 --- a/src/rlx_state.erl +++ b/src/rlx_state.erl @@ -25,7 +25,7 @@ -export([new/2, log/1, - action/1, + actions/1, output_dir/1, lib_dirs/1, overrides/1, @@ -72,18 +72,18 @@ -record(state_t, {log :: rlx_log:t(), root_dir :: file:name(), caller :: caller(), - action :: atom(), + actions=[] :: [atom()], output_dir :: file:name(), lib_dirs=[] :: [file:name()], config_file=[] :: file:filename() | undefined, goals=[] :: [rlx_depsolver:constraint()], - providers = [] :: [rlx_provider:t()], - available_apps = [] :: [rlx_app_info:t()], + providers=[] :: [rlx_provider:t()], + available_apps=[] :: [rlx_app_info:t()], default_configured_release :: {rlx_release:name(), rlx_release:vsn()}, vm_args :: file:filename() | undefined, sys_config :: file:filename() | undefined, overrides :: [{AppName::atom(), Directory::file:filename()}], - skip_apps = [] :: [AppName::atom()], + skip_apps=[] :: [AppName::atom()], configured_releases :: releases(), realized_releases :: releases(), upfrom :: string() | binary() | undefined, @@ -106,17 +106,17 @@ %% API %%============================================================================ %% @doc Create a new 'log level' for the system --spec new(proplists:proplist(), atom()) -> t(). -new(PropList, Target) +-spec new(proplists:proplist(), [atom()]) -> t(). +new(PropList, Targets) when erlang:is_list(PropList), - erlang:is_atom(Target) -> + erlang:is_list(Targets) -> {ok, Root} = file:get_cwd(), State0 = #state_t{log = proplists:get_value(log, PropList, rlx_log:new(error)), output_dir=proplists:get_value(output_dir, PropList, ""), lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], config_file=proplists:get_value(config, PropList, undefined), - action = Target, + actions = Targets, caller = proplists:get_value(caller, PropList, api), goals=proplists:get_value(goals, PropList, []), providers = [], @@ -132,10 +132,10 @@ new(PropList, Target) disable_default_libs, proplists:get_value(disable_default_libs, PropList, false)). -%% @doc the action targeted for this system --spec action(t()) -> atom(). -action(#state_t{action=Action}) -> - Action. +%% @doc the actions targeted for this system +-spec actions(t()) -> atom(). +actions(#state_t{actions=Actions}) -> + Actions. %% @doc the application overrides for the system -spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}]. @@ -359,7 +359,7 @@ to_binary(Dir) new_test() -> LogState = rlx_log:new(error), - RCLState = new([{log, LogState}], release), + RCLState = new([{log, LogState}], [release]), ?assertMatch(LogState, log(RCLState)). -endif. diff --git a/src/rlx_util.erl b/src/rlx_util.erl index ac6af5c..c821896 100644 --- a/src/rlx_util.erl +++ b/src/rlx_util.erl @@ -21,7 +21,8 @@ %%% @doc Trivial utility file to help handle common tasks -module(rlx_util). --export([mkdir_p/1, +-export([delete_dir/1, + mkdir_p/1, to_binary/1, to_string/1, to_atom/1, @@ -39,6 +40,19 @@ %%============================================================================ %% API %%============================================================================ +%% @doc Deletes non-empty directory +delete_dir(Path) -> + lists:foldr(fun(File, ok) -> + case filelib:is_dir(File) of + true -> + file:del_dir(File); + false -> + file:delete(File) + end + end, ok, filelib:wildcard(filename:join(Path, "**"))), + ok = file:del_dir(Path). + + %% @doc Makes a directory including parent dirs if they are missing. -spec mkdir_p(string()) -> ok | {error, Reason::file:posix()}. mkdir_p(Path) -> diff --git a/test/rlx_discover_SUITE.erl b/test/rlx_discover_SUITE.erl index d225ee6..d404d8c 100644 --- a/test/rlx_discover_SUITE.erl +++ b/test/rlx_discover_SUITE.erl @@ -46,7 +46,7 @@ init_per_testcase(_, Config) -> LibDir2 = filename:join([DataDir, create_random_name("lib_dir2_")]), ok = rlx_util:mkdir_p(LibDir1), ok = rlx_util:mkdir_p(LibDir2), - State = rlx_state:new([{lib_dirs, [LibDir1, LibDir2]}], release), + State = rlx_state:new([{lib_dirs, [LibDir1, LibDir2]}], [release]), [{lib1, LibDir1}, {lib2, LibDir2}, {state, State} | Config]. diff --git a/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl index ea4b20d..ccf5a8f 100644 --- a/test/rlx_release_SUITE.erl +++ b/test/rlx_release_SUITE.erl @@ -56,7 +56,7 @@ init_per_testcase(_, Config) -> DataDir = proplists:get_value(data_dir, Config), LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), ok = rlx_util:mkdir_p(LibDir1), - State = rlx_state:new([{lib_dirs, [LibDir1]}], release), + State = rlx_state:new([{lib_dirs, [LibDir1]}], [release]), [{lib1, LibDir1}, {state, State} | Config]. @@ -602,7 +602,7 @@ make_relup_release(Config) -> {lib_dirs, [LibDir1]}, {log_level, 2}, {output_dir, OutputDir}, - {config, ConfigFile}], ["relup"]), + {config, ConfigFile}], ["release", "relup"]), %% we should have one 'resolved' release and three discovered realized_releases. ?assertMatch([{foo, "0.0.1"}, @@ -619,9 +619,7 @@ make_relup_release(Config) -> ?assertMatch({ok, [{"0.0.3", [{"0.0.2",[],[point_of_no_return]}], [{"0.0.2",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), - filename:basename(rlx_release:relfile(Release), ".rel") ++ - ".relup"))), + file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), "relup"))), ?assertMatch(foo, rlx_release:name(Release)), ?assertMatch("0.0.3", rlx_release:vsn(Release)), @@ -685,7 +683,7 @@ make_relup_release2(Config) -> {lib_dirs, [LibDir1]}, {log_level, 2}, {output_dir, OutputDir}, - {config, ConfigFile}], ["relup"]), + {config, ConfigFile}], ["release", "relup"]), %% we should have one 'resolved' release and three discovered realized_releases. ?assertMatch([{foo, "0.0.1"}, @@ -702,9 +700,7 @@ make_relup_release2(Config) -> ?assertMatch({ok, [{"0.0.3", [{"0.0.1",[],[point_of_no_return]}], [{"0.0.1",[],[point_of_no_return]}]}]}, - file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), - filename:basename(rlx_release:relfile(Release), ".rel") ++ - ".relup"))), + file:consult(filename:join(filename:dirname(rlx_release:relfile(Release)), "relup"))), ?assertMatch(foo, rlx_release:name(Release)), ?assertMatch("0.0.3", rlx_release:vsn(Release)), |