aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rcl_cmd_args.erl12
-rw-r--r--src/rcl_prv_assembler.erl158
-rw-r--r--src/rcl_rel_discovery.erl6
-rw-r--r--src/rcl_release.erl30
-rw-r--r--src/rcl_state.erl11
-rw-r--r--src/rcl_util.erl3
-rw-r--r--src/relcool.erl3
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,