diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rcl_cmd_args.erl | 12 | ||||
-rw-r--r-- | src/rcl_prv_assembler.erl | 158 | ||||
-rw-r--r-- | src/rcl_rel_discovery.erl | 6 | ||||
-rw-r--r-- | src/rcl_release.erl | 30 | ||||
-rw-r--r-- | src/rcl_state.erl | 11 | ||||
-rw-r--r-- | src/rcl_util.erl | 3 | ||||
-rw-r--r-- | src/relcool.erl | 3 |
7 files changed, 194 insertions, 29 deletions
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index f5ebecd..fee4449 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -238,7 +238,17 @@ create_root_dir(Opts, Acc) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_disable_default_libs(Opts, Acc) -> Def = proplists:get_value(disable_default_libs, Opts, false), - create_caller(Opts, [{disable_default_libs, Def} | Acc]). + create_upfrom(Opts, [{disable_default_libs, Def} | Acc]). + +-spec create_upfrom([getopt:option()], rcl:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_upfrom(Opts, Acc) -> + case proplists:get_value(upfrom, Opts, undefined) of + undefined -> + create_caller(Opts, Acc); + UpFrom -> + create_caller(Opts, [{upfrom, UpFrom} | Acc]) + end. -spec create_caller([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 204c8bd..aca783d 100644 --- a/src/rcl_prv_assembler.erl +++ b/src/rcl_prv_assembler.erl @@ -80,6 +80,20 @@ format_error({unable_to_create_output_dir, OutputDir}) -> format_error({release_script_generation_error, Module, Errors}) -> ["Errors generating release \n", rcl_util:indent(1), Module:format_error(Errors)]; +format_error({relup_generation_error, CurrentName, UpFromName}) -> + io_lib:format("Unknown internal release error generating the relup from ~s to ~s", + [UpFromName, CurrentName]); +format_error({relup_generation_warning, Module, Warnings}) -> + ["Warnings generating relup \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({relup_script_generation_error, + {relupcript_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."; +format_error({relup_script_generation_error, Module, Errors}) -> + ["Errors generating relup \n", + 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), @@ -193,19 +207,17 @@ copy_dir(AppDir, TargetDir, SubDir) -> ok end. -create_release_info(State, Release, OutputDir) -> - RelName = erlang:atom_to_list(rcl_release:name(Release)), - ReleaseDir = filename:join([OutputDir, - "releases", - RelName ++ "-" ++ - rcl_release:vsn(Release)]), +create_release_info(State0, Release0, OutputDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release0)), + ReleaseDir = release_output_dir(State0, Release0), ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), ok = ec_file:mkdir_p(ReleaseDir), - case rcl_release:metadata(Release) of + Release1 = rcl_release:relfile(Release0, ReleaseFile), State1 = rcl_state:update_realized_release(State0, Release1), + case rcl_release:metadata(Release1) of {ok, Meta} -> ok = ec_file:write_term(ReleaseFile, Meta), - write_bin_file(State, Release, OutputDir, ReleaseDir); + write_bin_file(State1, Release1, OutputDir, ReleaseDir); E -> E end. @@ -216,7 +228,7 @@ write_bin_file(State, Release, OutputDir, RelDir) -> RelVsn = rcl_release:vsn(Release), BinDir = filename:join([OutputDir, "bin"]), ok = ec_file:mkdir_p(BinDir), - VsnRel = filename:join(BinDir, RelName ++ "-" ++ RelVsn), + VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), BareRel = filename:join(BinDir, RelName), ErlOpts = rcl_state:get(State, erl_opts, ""), StartFile = case rcl_state:get(State, extended_start_script, false) of @@ -320,42 +332,142 @@ make_boot_script(State, Release, OutputDir, RelDir) -> no_module_tests, silent], Name = erlang:atom_to_list(rcl_release:name(Release)), ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), - rcl_log:debug(rcl_state:log(State), - "Creating script from release file ~s ~n with options ~p ~n", - [ReleaseFile, Options]), - case make_script(Name, Options) of + case make_script(Options, + fun(CorrectedOptions) -> + systools:make_script(Name, CorrectedOptions) + end) of ok -> rcl_log:error(rcl_state:log(State), "release successfully created!"), - {ok, State}; + make_relup(State, Release); error -> ?RCL_ERROR({release_script_generation_error, ReleaseFile}); {ok, _, []} -> rcl_log:error(rcl_state:log(State), "release successfully created!"), - {ok, State}; + make_relup(State, Release); {ok,Module,Warnings} -> ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); {error,Module,Error} -> ?RCL_ERROR({release_script_generation_error, Module, Error}) end. --spec make_script(string(), [term()]) -> - ok | - error | - {ok, module(), [term()]} | - {error,module,[term()]}. -make_script(Name, Options) -> +-spec make_script([term()], + fun(([term()]) -> Res)) -> Res. +make_script(Options, RunFun) -> %% Erts 5.9 introduced a non backwards compatible option to %% erlang this takes that into account Erts = erlang:system_info(version), case ec_semver:gte(Erts, "5.9") of true -> - systools:make_script(Name, [no_warn_sasl | Options]); + RunFun([no_warn_sasl | Options]); + _ -> + RunFun(Options) + end. + +make_relup(State, Release) -> + case rcl_state:action(State) of + relup -> + UpFrom = + case rcl_state:upfrom(State) of + undefined -> + get_last_release(State, Release); + Vsn -> + get_up_release(State, Release, Vsn) + end, + case UpFrom of + undefined -> + ?RCL_ERROR(no_upfrom_release_found); + _ -> + make_upfrom_script(State, Release, UpFrom) + end; _ -> - systools:make_script(Name, Options) + {ok, State} end. +make_upfrom_script(State, Release, UpFrom) -> + OutputDir = rcl_state:output_dir(State), + Options = [{outdir, OutputDir}, + {path, get_code_paths(Release, OutputDir) ++ + get_code_paths(UpFrom, OutputDir)}, + silent], + CurrentRel = strip_rel(rcl_release:relfile(Release)), + UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), + rcl_log:debug(rcl_state:log(State), + "systools:make_relup(~p, ~p, ~p, ~p)", + [CurrentRel, UpFromRel, UpFromRel, Options]), + case make_script(Options, + fun(CorrectOptions) -> + systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "relup from ~s to ~s successfully created!", + [UpFromRel, CurrentRel]), + {ok, State}; + error -> + ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); + {ok, RelUp, _, []} -> + rcl_log:error(rcl_state:log(State), + "relup successfully created!"), + write_relup_file(State, Release, RelUp), + {ok, State}; + {ok,_, Module,Warnings} -> + ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); + {error,Module,Errors} -> + ?RCL_ERROR({relupcript_generation_error, Module, Errors}) + end. + +write_relup_file(State, Release, Relup) -> + OutDir = release_output_dir(State, Release), + RelName = rcl_util:to_string(rcl_release:name(Release)), + RelupFile = filename:join(OutDir, RelName ++ ".relup"), + ok = ec_file:write_term(RelupFile, Relup). + +strip_rel(Name) -> + rcl_util:to_string(filename:join(filename:dirname(Name), + filename:basename(Name, ".rel"))). + + +get_up_release(State, Release, Vsn) -> + Name = rcl_release:name(Release), + try + ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) + catch + throw:notfound -> + undefined + end. + +get_last_release(State, Release) -> + Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], + Releases1 = lists:sort(fun(R1, R2) -> + ec_semver:lte(rcl_release:vsn(R1), + rcl_release:vsn(R2)) + end, Releases0), + Res = lists:foldl(fun(_Rel, R = {found, _}) -> + R; + (Rel, Prev) -> + case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of + true -> + {found, Prev}; + false -> + Rel + end + end, undefined, Releases1), + case Res of + {found, R} -> + R; + Else -> + Else + end. + +-spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). +release_output_dir(State, Release) -> + OutputDir = rcl_state:output_dir(State), + filename:join([OutputDir, + "releases", + rcl_release:canonical_name(Release)]). + %% @doc Generates the correct set of code paths for the system. -spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. get_code_paths(Release, OutDir) -> diff --git a/src/rcl_rel_discovery.erl b/src/rcl_rel_discovery.erl index d9012ea..6cd84f0 100644 --- a/src/rcl_rel_discovery.erl +++ b/src/rcl_rel_discovery.erl @@ -118,15 +118,15 @@ resolve_release(RelFile, AppMeta) -> {ok, [{release, {RelName, RelVsn}, {erts, ErtsVsn}, Apps}]} -> - build_release(RelName, RelVsn, ErtsVsn, Apps, AppMeta); + build_release(RelFile, 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), +build_release(RelFile, RelName, RelVsn, ErtsVsn, Apps, AppMeta) -> + Release = rcl_release:erts(rcl_release:new(RelName, RelVsn, RelFile), ErtsVsn), resolve_apps(Apps, AppMeta, Release, []). diff --git a/src/rcl_release.erl b/src/rcl_release.erl index 97465d0..9ed741e 100644 --- a/src/rcl_release.erl +++ b/src/rcl_release.erl @@ -23,6 +23,9 @@ -module(rcl_release). -export([new/2, + new/3, + relfile/1, + relfile/2, erts/2, erts/1, goals/2, @@ -35,6 +38,7 @@ application_details/2, realized/1, metadata/1, + canonical_name/1, format/1, format/2, format_error/1]). @@ -57,6 +61,7 @@ realized = false :: boolean(), annotations = undefined :: annotations(), applications = [] :: [application_spec()], + relfile :: undefined | string(), app_detail = [] :: [rcl_app_info:t()]}). %%============================================================================ @@ -87,11 +92,25 @@ %%============================================================================ %% API %%============================================================================ --spec new(atom(), string()) -> t(). -new(ReleaseName, ReleaseVsn) -> +-spec new(atom(), string(), undefined | file:name()) -> t(). +new(ReleaseName, ReleaseVsn, Relfile) -> #release_t{name=to_atom(ReleaseName), vsn=ReleaseVsn, + relfile = Relfile, annotations=ec_dictionary:new(ec_dict)}. +-spec new(atom(), string()) -> t(). +new(ReleaseName, ReleaseVsn) -> + new(ReleaseName, ReleaseVsn, undefined). + + +-spec relfile(t()) -> file:name() | undefined. +relfile(#release_t{relfile=Relfile}) -> + Relfile. + +-spec relfile(t(), file:name()) -> t(). +relfile(Release, Relfile) -> + Release#release_t{relfile=Relfile}. + -spec name(t()) -> atom(). name(#release_t{name=Name}) -> Name. @@ -162,6 +181,12 @@ metadata(#release_t{name=Name, vsn=Vsn, erts=ErtsVsn, applications=Apps, ?RCL_ERROR({not_realized, Name, Vsn}) end. +%% @doc produce the canonical name (<name>-<vsn>) for this release +-spec canonical_name(t()) -> string(). +canonical_name(#release_t{name=Name, vsn=Vsn}) -> + erlang:binary_to_list(erlang:iolist_to_binary([erlang:atom_to_list(Name), "-", + Vsn])). + -spec format(t()) -> iolist(). format(Release) -> format(0, Release). @@ -183,6 +208,7 @@ format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized, false -> [] end]. + -spec format_goal(application_goal()) -> iolist(). format_goal({Constraint, AppType}) -> io_lib:format("~p", [{rcl_depsolver:format_constraint(Constraint), AppType}]); diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 3a91f48..ca6ec8c 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -25,6 +25,7 @@ -export([new/2, log/1, + action/1, output_dir/1, lib_dirs/1, overrides/1, @@ -57,6 +58,7 @@ put/3, caller/1, caller/2, + upfrom/1, format/1, format/2]). @@ -127,6 +129,11 @@ 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 application overrides for the system -spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}]. overrides(#state_t{overrides=Overrides}) -> @@ -282,6 +289,10 @@ caller(#state_t{caller=Caller}) -> caller(S, Caller) -> S#state_t{caller=Caller}. +-spec upfrom(t()) -> string() | binary() | undefined. +upfrom(#state_t{upfrom=UpFrom}) -> + UpFrom. + -spec format(t()) -> iolist(). format(Mod) -> format(Mod, 0). diff --git a/src/rcl_util.erl b/src/rcl_util.erl index 61e1392..4afb26d 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -58,6 +58,9 @@ to_binary(String) when erlang:is_list(String) -> erlang:iolist_to_binary(String); to_binary(Bin) when erlang:is_binary(Bin) -> Bin. + +to_string(Binary) when erlang:is_binary(Binary) -> + erlang:binary_to_list(Binary); to_string(Atom) when erlang:is_atom(Atom) -> erlang:atom_to_list(Atom); to_string(Else) when erlang:is_list(Else) -> diff --git a/src/relcool.erl b/src/relcool.erl index 0079529..c404e6f 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -21,6 +21,7 @@ -module(relcool). -export([main/1, + do/2, do/7, do/8, do/9, @@ -169,6 +170,8 @@ opt_spec_list() -> {relvsn, $v, "relvsn", string, "Specify the version for the release"}, {goal, $g, "goal", string, "Specify a target constraint on the system. These are usually the OTP"}, + {upfrom, $u, "upfrom", string, + "Only valid with relup target, specify the release to upgrade from"}, {output_dir, $o, "output-dir", string, "The output directory for the release. This is `./` by default."}, {lib_dir, $l, "lib-dir", string, |