style: erlfmt all remaining escripts
This commit is contained in:
parent
3fd5ab2782
commit
72eb34658d
|
@ -4,9 +4,11 @@
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
%% ex: ft=erlang ts=4 sw=4 et
|
||||||
|
|
||||||
-define(TIMEOUT, 300000).
|
-define(TIMEOUT, 300000).
|
||||||
-define(INFO(Fmt,Args), io:format(standard_io, Fmt++"~n",Args)).
|
-define(INFO(Fmt, Args), io:format(standard_io, Fmt ++ "~n", Args)).
|
||||||
-define(ERROR(Fmt,Args), io:format(standard_error, "ERROR: "++Fmt++"~n",Args)).
|
-define(ERROR(Fmt, Args), io:format(standard_error, "ERROR: " ++ Fmt ++ "~n", Args)).
|
||||||
-define(SEMVER_RE, <<"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-[a-zA-Z\\d][-a-zA-Z.\\d]*)?(\\+[a-zA-Z\\d][-a-zA-Z.\\d]*)?$">>).
|
-define(SEMVER_RE,
|
||||||
|
<<"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-[a-zA-Z\\d][-a-zA-Z.\\d]*)?(\\+[a-zA-Z\\d][-a-zA-Z.\\d]*)?$">>
|
||||||
|
).
|
||||||
|
|
||||||
-mode(compile).
|
-mode(compile).
|
||||||
|
|
||||||
|
@ -17,7 +19,8 @@ main([Command0, DistInfoStr | CommandArgs]) ->
|
||||||
%% convert arguments into a proplist
|
%% convert arguments into a proplist
|
||||||
Opts = parse_arguments(CommandArgs),
|
Opts = parse_arguments(CommandArgs),
|
||||||
%% invoke the command passed as argument
|
%% invoke the command passed as argument
|
||||||
F = case Command0 of
|
F =
|
||||||
|
case Command0 of
|
||||||
"install" -> fun(A, B) -> install(A, B) end;
|
"install" -> fun(A, B) -> install(A, B) end;
|
||||||
"unpack" -> fun(A, B) -> unpack(A, B) end;
|
"unpack" -> fun(A, B) -> unpack(A, B) end;
|
||||||
"upgrade" -> fun(A, B) -> upgrade(A, B) end;
|
"upgrade" -> fun(A, B) -> upgrade(A, B) end;
|
||||||
|
@ -38,15 +41,15 @@ unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
?INFO("Unpacked successfully: ~p", [Vsn]);
|
?INFO("Unpacked successfully: ~p", [Vsn]);
|
||||||
old ->
|
old ->
|
||||||
%% no need to unpack, has been installed previously
|
%% no need to unpack, has been installed previously
|
||||||
?INFO("Release ~s is marked old.",[Version]);
|
?INFO("Release ~s is marked old.", [Version]);
|
||||||
unpacked ->
|
unpacked ->
|
||||||
?INFO("Release ~s is already unpacked.",[Version]);
|
?INFO("Release ~s is already unpacked.", [Version]);
|
||||||
current ->
|
current ->
|
||||||
?INFO("Release ~s is already installed and current.",[Version]);
|
?INFO("Release ~s is already installed and current.", [Version]);
|
||||||
permanent ->
|
permanent ->
|
||||||
?INFO("Release ~s is already installed and set permanent.",[Version]);
|
?INFO("Release ~s is already installed and set permanent.", [Version]);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?INFO("Unpack failed: ~p.",[Reason]),
|
?INFO("Unpack failed: ~p.", [Reason]),
|
||||||
print_existing_versions(TargetNode),
|
print_existing_versions(TargetNode),
|
||||||
erlang:halt(2)
|
erlang:halt(2)
|
||||||
end;
|
end;
|
||||||
|
@ -64,38 +67,46 @@ install({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
maybe_permafy(TargetNode, RelName, Vsn, Opts);
|
maybe_permafy(TargetNode, RelName, Vsn, Opts);
|
||||||
old ->
|
old ->
|
||||||
%% no need to unpack, has been installed previously
|
%% no need to unpack, has been installed previously
|
||||||
?INFO("Release ~s is marked old, switching to it.",[Version]),
|
?INFO("Release ~s is marked old, switching to it.", [Version]),
|
||||||
check_and_install(TargetNode, Version),
|
check_and_install(TargetNode, Version),
|
||||||
maybe_permafy(TargetNode, RelName, Version, Opts);
|
maybe_permafy(TargetNode, RelName, Version, Opts);
|
||||||
unpacked ->
|
unpacked ->
|
||||||
?INFO("Release ~s is already unpacked, now installing.",[Version]),
|
?INFO("Release ~s is already unpacked, now installing.", [Version]),
|
||||||
check_and_install(TargetNode, Version),
|
check_and_install(TargetNode, Version),
|
||||||
maybe_permafy(TargetNode, RelName, Version, Opts);
|
maybe_permafy(TargetNode, RelName, Version, Opts);
|
||||||
current ->
|
current ->
|
||||||
case proplists:get_value(permanent, Opts, true) of
|
case proplists:get_value(permanent, Opts, true) of
|
||||||
true ->
|
true ->
|
||||||
?INFO("Release ~s is already installed and current, making permanent.",
|
?INFO(
|
||||||
[Version]),
|
"Release ~s is already installed and current, making permanent.",
|
||||||
|
[Version]
|
||||||
|
),
|
||||||
permafy(TargetNode, RelName, Version);
|
permafy(TargetNode, RelName, Version);
|
||||||
false ->
|
false ->
|
||||||
?INFO("Release ~s is already installed and current.",
|
?INFO(
|
||||||
[Version])
|
"Release ~s is already installed and current.",
|
||||||
|
[Version]
|
||||||
|
)
|
||||||
end;
|
end;
|
||||||
permanent ->
|
permanent ->
|
||||||
%% this release is marked permanent, however it might not the
|
%% this release is marked permanent, however it might not the
|
||||||
%% one currently running
|
%% one currently running
|
||||||
case current_release_version(TargetNode) of
|
case current_release_version(TargetNode) of
|
||||||
Version ->
|
Version ->
|
||||||
?INFO("Release ~s is already installed, running and set permanent.",
|
?INFO(
|
||||||
[Version]);
|
"Release ~s is already installed, running and set permanent.",
|
||||||
|
[Version]
|
||||||
|
);
|
||||||
CurrentVersion ->
|
CurrentVersion ->
|
||||||
?INFO("Release ~s is the currently running version.",
|
?INFO(
|
||||||
[CurrentVersion]),
|
"Release ~s is the currently running version.",
|
||||||
|
[CurrentVersion]
|
||||||
|
),
|
||||||
check_and_install(TargetNode, Version),
|
check_and_install(TargetNode, Version),
|
||||||
maybe_permafy(TargetNode, RelName, Version, Opts)
|
maybe_permafy(TargetNode, RelName, Version, Opts)
|
||||||
end;
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?INFO("Unpack failed: ~p",[Reason]),
|
?INFO("Unpack failed: ~p", [Reason]),
|
||||||
print_existing_versions(TargetNode),
|
print_existing_versions(TargetNode),
|
||||||
erlang:halt(2)
|
erlang:halt(2)
|
||||||
end;
|
end;
|
||||||
|
@ -119,8 +130,10 @@ uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
?INFO("Release ~s is marked old, uninstalling it.", [Version]),
|
?INFO("Release ~s is marked old, uninstalling it.", [Version]),
|
||||||
remove_release(TargetNode, Version);
|
remove_release(TargetNode, Version);
|
||||||
unpacked ->
|
unpacked ->
|
||||||
?INFO("Release ~s is marked unpacked, uninstalling it",
|
?INFO(
|
||||||
[Version]),
|
"Release ~s is marked unpacked, uninstalling it",
|
||||||
|
[Version]
|
||||||
|
),
|
||||||
remove_release(TargetNode, Version);
|
remove_release(TargetNode, Version);
|
||||||
current ->
|
current ->
|
||||||
?INFO("Uninstall failed: Release ~s is marked current.", [Version]),
|
?INFO("Uninstall failed: Release ~s is marked current.", [Version]),
|
||||||
|
@ -140,10 +153,11 @@ parse_arguments(Args) ->
|
||||||
IsEnterprise = os:getenv("IS_ENTERPRISE") == "yes",
|
IsEnterprise = os:getenv("IS_ENTERPRISE") == "yes",
|
||||||
parse_arguments(Args, [{is_enterprise, IsEnterprise}]).
|
parse_arguments(Args, [{is_enterprise, IsEnterprise}]).
|
||||||
|
|
||||||
parse_arguments([], Acc) -> Acc;
|
parse_arguments([], Acc) ->
|
||||||
parse_arguments(["--no-permanent"|Rest], Acc) ->
|
Acc;
|
||||||
|
parse_arguments(["--no-permanent" | Rest], Acc) ->
|
||||||
parse_arguments(Rest, [{permanent, false}] ++ Acc);
|
parse_arguments(Rest, [{permanent, false}] ++ Acc);
|
||||||
parse_arguments([VersionStr|Rest], Acc) ->
|
parse_arguments([VersionStr | Rest], Acc) ->
|
||||||
Version = parse_version(VersionStr),
|
Version = parse_version(VersionStr),
|
||||||
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
||||||
|
|
||||||
|
@ -162,18 +176,29 @@ unpack_release(RelName, TargetNode, Version, Opts) ->
|
||||||
{_, undefined} ->
|
{_, undefined} ->
|
||||||
{error, release_package_not_found};
|
{error, release_package_not_found};
|
||||||
{ReleasePackage, ReleasePackageLink} ->
|
{ReleasePackage, ReleasePackageLink} ->
|
||||||
?INFO("Release ~s not found, attempting to unpack ~s",
|
?INFO(
|
||||||
[Version, ReleasePackage]),
|
"Release ~s not found, attempting to unpack ~s",
|
||||||
case rpc:call(TargetNode, release_handler, unpack_release,
|
[Version, ReleasePackage]
|
||||||
[ReleasePackageLink], ?TIMEOUT) of
|
),
|
||||||
{ok, Vsn} -> {ok, Vsn};
|
case
|
||||||
|
rpc:call(
|
||||||
|
TargetNode,
|
||||||
|
release_handler,
|
||||||
|
unpack_release,
|
||||||
|
[ReleasePackageLink],
|
||||||
|
?TIMEOUT
|
||||||
|
)
|
||||||
|
of
|
||||||
|
{ok, Vsn} ->
|
||||||
|
{ok, Vsn};
|
||||||
{error, {existing_release, Vsn}} ->
|
{error, {existing_release, Vsn}} ->
|
||||||
%% sometimes the user may have removed the release/<vsn> dir
|
%% sometimes the user may have removed the release/<vsn> dir
|
||||||
%% for an `unpacked` release, then we need to re-unpack it from
|
%% for an `unpacked` release, then we need to re-unpack it from
|
||||||
%% the .tar ball
|
%% the .tar ball
|
||||||
untar_for_unpacked_release(str(RelName), Vsn),
|
untar_for_unpacked_release(str(RelName), Vsn),
|
||||||
{ok, Vsn};
|
{ok, Vsn};
|
||||||
{error, _} = Error -> Error
|
{error, _} = Error ->
|
||||||
|
Error
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
Other ->
|
Other ->
|
||||||
|
@ -198,8 +223,8 @@ untar_for_unpacked_release(RelName, Vsn) ->
|
||||||
extract_tar(Cwd, Tar) ->
|
extract_tar(Cwd, Tar) ->
|
||||||
case erl_tar:extract(Tar, [keep_old_files, {cwd, Cwd}, compressed]) of
|
case erl_tar:extract(Tar, [keep_old_files, {cwd, Cwd}, compressed]) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
{error, {Name, Reason}} -> % New erl_tar (R3A).
|
% New erl_tar (R3A).
|
||||||
throw({error, {cannot_extract_file, Name, Reason}})
|
{error, {Name, Reason}} -> throw({error, {cannot_extract_file, Name, Reason}})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% 1. look for a release package tarball with the provided version:
|
%% 1. look for a release package tarball with the provided version:
|
||||||
|
@ -217,8 +242,11 @@ find_and_link_release_package(Version, RelName, IsEnterprise) ->
|
||||||
ReleaseHandlerPackageLink = filename:join(Version, RelNameStr),
|
ReleaseHandlerPackageLink = filename:join(Version, RelNameStr),
|
||||||
%% this is the symlink name we'll create once
|
%% this is the symlink name we'll create once
|
||||||
%% we've found where the actual release package is located
|
%% we've found where the actual release package is located
|
||||||
ReleaseLink = filename:join(["releases", Version,
|
ReleaseLink = filename:join([
|
||||||
RelNameStr ++ ".tar.gz"]),
|
"releases",
|
||||||
|
Version,
|
||||||
|
RelNameStr ++ ".tar.gz"
|
||||||
|
]),
|
||||||
ReleaseNamePattern =
|
ReleaseNamePattern =
|
||||||
case IsEnterprise of
|
case IsEnterprise of
|
||||||
false -> RelNameStr;
|
false -> RelNameStr;
|
||||||
|
@ -240,14 +268,18 @@ find_and_link_release_package(Version, RelName, IsEnterprise) ->
|
||||||
make_symlink_or_copy(filename:absname(Filename), ReleaseLink),
|
make_symlink_or_copy(filename:absname(Filename), ReleaseLink),
|
||||||
{Filename, ReleaseHandlerPackageLink};
|
{Filename, ReleaseHandlerPackageLink};
|
||||||
Files ->
|
Files ->
|
||||||
?ERROR("Found more than one package for version: '~s', "
|
?ERROR(
|
||||||
"files: ~p", [Version, Files]),
|
"Found more than one package for version: '~s', "
|
||||||
|
"files: ~p",
|
||||||
|
[Version, Files]
|
||||||
|
),
|
||||||
erlang:halt(47)
|
erlang:halt(47)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
make_symlink_or_copy(Filename, ReleaseLink) ->
|
make_symlink_or_copy(Filename, ReleaseLink) ->
|
||||||
case file:make_symlink(Filename, ReleaseLink) of
|
case file:make_symlink(Filename, ReleaseLink) of
|
||||||
ok -> ok;
|
ok ->
|
||||||
|
ok;
|
||||||
{error, eexist} ->
|
{error, eexist} ->
|
||||||
?INFO("Symlink ~p already exists, recreate it", [ReleaseLink]),
|
?INFO("Symlink ~p already exists, recreate it", [ReleaseLink]),
|
||||||
ok = file:delete(ReleaseLink),
|
ok = file:delete(ReleaseLink),
|
||||||
|
@ -260,36 +292,55 @@ make_symlink_or_copy(Filename, ReleaseLink) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_version(V) when is_list(V) ->
|
parse_version(V) when is_list(V) ->
|
||||||
hd(string:tokens(V,"/")).
|
hd(string:tokens(V, "/")).
|
||||||
|
|
||||||
check_and_install(TargetNode, Vsn) ->
|
check_and_install(TargetNode, Vsn) ->
|
||||||
%% Backup the sys.config, this will be used when we check and install release
|
%% Backup the sys.config, this will be used when we check and install release
|
||||||
%% NOTE: We cannot backup the old sys.config directly, because the
|
%% NOTE: We cannot backup the old sys.config directly, because the
|
||||||
%% configs for plugins are only in app-envs, not in the old sys.config
|
%% configs for plugins are only in app-envs, not in the old sys.config
|
||||||
Configs0 =
|
Configs0 =
|
||||||
[{AppName, rpc:call(TargetNode, application, get_all_env, [AppName], ?TIMEOUT)}
|
[
|
||||||
|| {AppName, _, _} <- rpc:call(TargetNode, application, which_applications, [], ?TIMEOUT)],
|
{AppName, rpc:call(TargetNode, application, get_all_env, [AppName], ?TIMEOUT)}
|
||||||
|
|| {AppName, _, _} <- rpc:call(TargetNode, application, which_applications, [], ?TIMEOUT)
|
||||||
|
],
|
||||||
Configs1 = [{AppName, Conf} || {AppName, Conf} <- Configs0, Conf =/= []],
|
Configs1 = [{AppName, Conf} || {AppName, Conf} <- Configs0, Conf =/= []],
|
||||||
ok = file:write_file(filename:join(["releases", Vsn, "sys.config"]), io_lib:format("~p.", [Configs1])),
|
ok = file:write_file(
|
||||||
|
filename:join(["releases", Vsn, "sys.config"]), io_lib:format("~p.", [Configs1])
|
||||||
|
),
|
||||||
|
|
||||||
%% check and install release
|
%% check and install release
|
||||||
case rpc:call(TargetNode, release_handler,
|
case
|
||||||
check_install_release, [Vsn], ?TIMEOUT) of
|
rpc:call(
|
||||||
|
TargetNode,
|
||||||
|
release_handler,
|
||||||
|
check_install_release,
|
||||||
|
[Vsn],
|
||||||
|
?TIMEOUT
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, _OtherVsn, _Desc} ->
|
{ok, _OtherVsn, _Desc} ->
|
||||||
ok;
|
ok;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?ERROR("Call release_handler:check_install_release failed: ~p.", [Reason]),
|
?ERROR("Call release_handler:check_install_release failed: ~p.", [Reason]),
|
||||||
erlang:halt(3)
|
erlang:halt(3)
|
||||||
end,
|
end,
|
||||||
case rpc:call(TargetNode, release_handler, install_release,
|
case
|
||||||
[Vsn, [{update_paths, true}]], ?TIMEOUT) of
|
rpc:call(
|
||||||
|
TargetNode,
|
||||||
|
release_handler,
|
||||||
|
install_release,
|
||||||
|
[Vsn, [{update_paths, true}]],
|
||||||
|
?TIMEOUT
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, _, _} ->
|
{ok, _, _} ->
|
||||||
?INFO("Installed Release: ~s.", [Vsn]),
|
?INFO("Installed Release: ~s.", [Vsn]),
|
||||||
ok;
|
ok;
|
||||||
{error, {no_such_release, Vsn}} ->
|
{error, {no_such_release, Vsn}} ->
|
||||||
VerList =
|
VerList =
|
||||||
iolist_to_binary(
|
iolist_to_binary(
|
||||||
[io_lib:format("* ~s\t~s~n",[V,S]) || {V,S} <- which_releases(TargetNode)]),
|
[io_lib:format("* ~s\t~s~n", [V, S]) || {V, S} <- which_releases(TargetNode)]
|
||||||
|
),
|
||||||
?INFO("Installed versions:~n~s", [VerList]),
|
?INFO("Installed versions:~n~s", [VerList]),
|
||||||
?ERROR("Unable to revert to '~s' - not installed.", [Vsn]),
|
?ERROR("Unable to revert to '~s' - not installed.", [Vsn]),
|
||||||
erlang:halt(2);
|
erlang:halt(2);
|
||||||
|
@ -298,11 +349,13 @@ check_and_install(TargetNode, Vsn) ->
|
||||||
%% If the value is soft_purge, release_handler:install_release/1
|
%% If the value is soft_purge, release_handler:install_release/1
|
||||||
%% returns {error,{old_processes,Mod}}
|
%% returns {error,{old_processes,Mod}}
|
||||||
{error, {old_processes, Mod}} ->
|
{error, {old_processes, Mod}} ->
|
||||||
?ERROR("Unable to install '~s' - old processes still running code from module ~p",
|
?ERROR(
|
||||||
[Vsn, Mod]),
|
"Unable to install '~s' - old processes still running code from module ~p",
|
||||||
|
[Vsn, Mod]
|
||||||
|
),
|
||||||
erlang:halt(3);
|
erlang:halt(3);
|
||||||
{error, Reason1} ->
|
{error, Reason1} ->
|
||||||
?ERROR("Call release_handler:install_release failed: ~p",[Reason1]),
|
?ERROR("Call release_handler:install_release failed: ~p", [Reason1]),
|
||||||
erlang:halt(4)
|
erlang:halt(4)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -310,22 +363,34 @@ maybe_permafy(TargetNode, RelName, Vsn, Opts) ->
|
||||||
case proplists:get_value(permanent, Opts, true) of
|
case proplists:get_value(permanent, Opts, true) of
|
||||||
true ->
|
true ->
|
||||||
permafy(TargetNode, RelName, Vsn);
|
permafy(TargetNode, RelName, Vsn);
|
||||||
false -> ok
|
false ->
|
||||||
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
permafy(TargetNode, RelName, Vsn) ->
|
permafy(TargetNode, RelName, Vsn) ->
|
||||||
RelNameStr = atom_to_list(RelName),
|
RelNameStr = atom_to_list(RelName),
|
||||||
ok = rpc:call(TargetNode, release_handler,
|
ok = rpc:call(
|
||||||
make_permanent, [Vsn], ?TIMEOUT),
|
TargetNode,
|
||||||
|
release_handler,
|
||||||
|
make_permanent,
|
||||||
|
[Vsn],
|
||||||
|
?TIMEOUT
|
||||||
|
),
|
||||||
?INFO("Made release permanent: ~p", [Vsn]),
|
?INFO("Made release permanent: ~p", [Vsn]),
|
||||||
%% upgrade/downgrade the scripts by replacing them
|
%% upgrade/downgrade the scripts by replacing them
|
||||||
Scripts = [RelNameStr, RelNameStr++"_ctl", "nodetool", "install_upgrade.escript"],
|
Scripts = [RelNameStr, RelNameStr ++ "_ctl", "nodetool", "install_upgrade.escript"],
|
||||||
[{ok, _} = file:copy(filename:join(["bin", File++"-"++Vsn]),
|
[
|
||||||
filename:join(["bin", File]))
|
{ok, _} = file:copy(
|
||||||
|| File <- Scripts],
|
filename:join(["bin", File ++ "-" ++ Vsn]),
|
||||||
|
filename:join(["bin", File])
|
||||||
|
)
|
||||||
|
|| File <- Scripts
|
||||||
|
],
|
||||||
%% update the vars
|
%% update the vars
|
||||||
UpdatedVars = io_lib:format("REL_VSN=\"~s\"~nERTS_VSN=\"~s\"~n", [Vsn, erts_vsn()]),
|
UpdatedVars = io_lib:format("REL_VSN=\"~s\"~nERTS_VSN=\"~s\"~n", [Vsn, erts_vsn()]),
|
||||||
file:write_file(filename:absname(filename:join(["releases", "emqx_vars"])), UpdatedVars, [append]).
|
file:write_file(filename:absname(filename:join(["releases", "emqx_vars"])), UpdatedVars, [
|
||||||
|
append
|
||||||
|
]).
|
||||||
|
|
||||||
remove_release(TargetNode, Vsn) ->
|
remove_release(TargetNode, Vsn) ->
|
||||||
case rpc:call(TargetNode, release_handler, remove_release, [Vsn], ?TIMEOUT) of
|
case rpc:call(TargetNode, release_handler, remove_release, [Vsn], ?TIMEOUT) of
|
||||||
|
@ -339,22 +404,31 @@ remove_release(TargetNode, Vsn) ->
|
||||||
|
|
||||||
which_releases(TargetNode) ->
|
which_releases(TargetNode) ->
|
||||||
R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT),
|
R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT),
|
||||||
[ {V, S} || {_,V,_, S} <- R ].
|
[{V, S} || {_, V, _, S} <- R].
|
||||||
|
|
||||||
%% the running release version is either the only one marked `current´
|
%% the running release version is either the only one marked `current´
|
||||||
%% or, if none exists, the one marked `permanent`
|
%% or, if none exists, the one marked `permanent`
|
||||||
current_release_version(TargetNode) ->
|
current_release_version(TargetNode) ->
|
||||||
R = rpc:call(TargetNode, release_handler, which_releases,
|
R = rpc:call(
|
||||||
[], ?TIMEOUT),
|
TargetNode,
|
||||||
Versions = [ {S, V} || {_,V,_, S} <- R ],
|
release_handler,
|
||||||
|
which_releases,
|
||||||
|
[],
|
||||||
|
?TIMEOUT
|
||||||
|
),
|
||||||
|
Versions = [{S, V} || {_, V, _, S} <- R],
|
||||||
%% current version takes priority over the permanent
|
%% current version takes priority over the permanent
|
||||||
proplists:get_value(current, Versions,
|
proplists:get_value(
|
||||||
proplists:get_value(permanent, Versions)).
|
current,
|
||||||
|
Versions,
|
||||||
|
proplists:get_value(permanent, Versions)
|
||||||
|
).
|
||||||
|
|
||||||
print_existing_versions(TargetNode) ->
|
print_existing_versions(TargetNode) ->
|
||||||
VerList = iolist_to_binary([
|
VerList = iolist_to_binary([
|
||||||
io_lib:format("* ~s\t~s~n",[V,S])
|
io_lib:format("* ~s\t~s~n", [V, S])
|
||||||
|| {V,S} <- which_releases(TargetNode) ]),
|
|| {V, S} <- which_releases(TargetNode)
|
||||||
|
]),
|
||||||
?INFO("Installed versions:~n~s", [VerList]).
|
?INFO("Installed versions:~n~s", [VerList]).
|
||||||
|
|
||||||
start_distribution(TargetNode, NameTypeArg, Cookie) ->
|
start_distribution(TargetNode, NameTypeArg, Cookie) ->
|
||||||
|
@ -393,11 +467,14 @@ erts_vsn() ->
|
||||||
validate_target_version(TargetVersion, TargetNode) ->
|
validate_target_version(TargetVersion, TargetNode) ->
|
||||||
CurrentVersion = current_release_version(TargetNode),
|
CurrentVersion = current_release_version(TargetNode),
|
||||||
case {get_major_minor_vsn(CurrentVersion), get_major_minor_vsn(TargetVersion)} of
|
case {get_major_minor_vsn(CurrentVersion), get_major_minor_vsn(TargetVersion)} of
|
||||||
{{Major, Minor}, {Major, Minor}} -> ok;
|
{{Major, Minor}, {Major, Minor}} ->
|
||||||
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
?ERROR("Cannot upgrade/downgrade from '~s' to '~s'~n"
|
?ERROR(
|
||||||
|
"Cannot upgrade/downgrade from '~s' to '~s'~n"
|
||||||
"Hot upgrade is only supported between patch releases.",
|
"Hot upgrade is only supported between patch releases.",
|
||||||
[CurrentVersion, TargetVersion]),
|
[CurrentVersion, TargetVersion]
|
||||||
|
),
|
||||||
erlang:halt(48)
|
erlang:halt(48)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -409,7 +486,8 @@ get_major_minor_vsn(Version) ->
|
||||||
|
|
||||||
parse_semver(Version) ->
|
parse_semver(Version) ->
|
||||||
case re:run(Version, ?SEMVER_RE, [{capture, all_but_first, binary}]) of
|
case re:run(Version, ?SEMVER_RE, [{capture, all_but_first, binary}]) of
|
||||||
{match, Parts} -> Parts;
|
{match, Parts} ->
|
||||||
|
Parts;
|
||||||
nomatch ->
|
nomatch ->
|
||||||
?ERROR("Invalid semantic version: '~s'~n", [Version]),
|
?ERROR("Invalid semantic version: '~s'~n", [Version]),
|
||||||
erlang:halt(22)
|
erlang:halt(22)
|
||||||
|
|
|
@ -20,7 +20,8 @@ apps_rebar_config(Dir) ->
|
||||||
|
|
||||||
%% collect a kv-list of {DepName, [{DepReference, RebarConfigFile}]}
|
%% collect a kv-list of {DepName, [{DepReference, RebarConfigFile}]}
|
||||||
%% the value part should have unique DepReference
|
%% the value part should have unique DepReference
|
||||||
collect_deps([], Acc) -> maps:to_list(Acc);
|
collect_deps([], Acc) ->
|
||||||
|
maps:to_list(Acc);
|
||||||
collect_deps([File | Files], Acc) ->
|
collect_deps([File | Files], Acc) ->
|
||||||
Deps =
|
Deps =
|
||||||
try
|
try
|
||||||
|
@ -28,12 +29,13 @@ collect_deps([File | Files], Acc) ->
|
||||||
{deps, Deps0} = lists:keyfind(deps, 1, Config),
|
{deps, Deps0} = lists:keyfind(deps, 1, Config),
|
||||||
Deps0
|
Deps0
|
||||||
catch
|
catch
|
||||||
C : E : St ->
|
C:E:St ->
|
||||||
erlang:raise(C, {E, {failed_to_find_deps_in_rebar_config, File}}, St)
|
erlang:raise(C, {E, {failed_to_find_deps_in_rebar_config, File}}, St)
|
||||||
end,
|
end,
|
||||||
collect_deps(Files, do_collect_deps(Deps, File, Acc)).
|
collect_deps(Files, do_collect_deps(Deps, File, Acc)).
|
||||||
|
|
||||||
do_collect_deps([], _File, Acc) -> Acc;
|
do_collect_deps([], _File, Acc) ->
|
||||||
|
Acc;
|
||||||
%% ignore relative app dependencies
|
%% ignore relative app dependencies
|
||||||
do_collect_deps([{_Name, {path, _Path}} | Deps], File, Acc) ->
|
do_collect_deps([{_Name, {path, _Path}} | Deps], File, Acc) ->
|
||||||
do_collect_deps(Deps, File, Acc);
|
do_collect_deps(Deps, File, Acc);
|
||||||
|
@ -41,7 +43,8 @@ do_collect_deps([{Name, Ref} | Deps], File, Acc) ->
|
||||||
Refs = maps:get(Name, Acc, []),
|
Refs = maps:get(Name, Acc, []),
|
||||||
do_collect_deps(Deps, File, Acc#{Name => [{Ref, File} | Refs]}).
|
do_collect_deps(Deps, File, Acc#{Name => [{Ref, File} | Refs]}).
|
||||||
|
|
||||||
count_bad_deps([]) -> 0;
|
count_bad_deps([]) ->
|
||||||
|
0;
|
||||||
count_bad_deps([{Name, Refs0} | Rest]) ->
|
count_bad_deps([{Name, Refs0} | Rest]) ->
|
||||||
Refs = lists:keysort(1, Refs0),
|
Refs = lists:keysort(1, Refs0),
|
||||||
case is_unique_ref(Refs) andalso not_branch_ref(Refs) of
|
case is_unique_ref(Refs) andalso not_branch_ref(Refs) of
|
||||||
|
@ -53,10 +56,8 @@ count_bad_deps([{Name, Refs0} | Rest]) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
is_unique_ref([_]) -> true;
|
is_unique_ref([_]) -> true;
|
||||||
is_unique_ref([{Ref, _File1}, {Ref, File2} | Rest]) ->
|
is_unique_ref([{Ref, _File1}, {Ref, File2} | Rest]) -> is_unique_ref([{Ref, File2} | Rest]);
|
||||||
is_unique_ref([{Ref, File2} | Rest]);
|
is_unique_ref(_) -> false.
|
||||||
is_unique_ref(_) ->
|
|
||||||
false.
|
|
||||||
|
|
||||||
not_branch_ref([]) -> true;
|
not_branch_ref([]) -> true;
|
||||||
not_branch_ref([{{git, _Repo, {branch, _Branch}}, _File} | _Rest]) -> false;
|
not_branch_ref([{{git, _Repo, {branch, _Branch}}, _File} | _Rest]) -> false;
|
||||||
|
|
|
@ -46,7 +46,6 @@ logerr(Fmt, Args) ->
|
||||||
_ = put(errors, N + 1),
|
_ = put(errors, N + 1),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
check(File) ->
|
check(File) ->
|
||||||
io:format(user, ".", []),
|
io:format(user, ".", []),
|
||||||
{ok, C} = hocon:load(File),
|
{ok, C} = hocon:load(File),
|
||||||
|
@ -54,9 +53,12 @@ check(File) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
check_one_field(Name, Field) ->
|
check_one_field(Name, Field) ->
|
||||||
maps:foreach(fun(SubName, DescAndLabel) ->
|
maps:foreach(
|
||||||
|
fun(SubName, DescAndLabel) ->
|
||||||
check_desc_and_label([Name, ".", SubName], DescAndLabel)
|
check_desc_and_label([Name, ".", SubName], DescAndLabel)
|
||||||
end, Field).
|
end,
|
||||||
|
Field
|
||||||
|
).
|
||||||
|
|
||||||
check_desc_and_label(Name, D) ->
|
check_desc_and_label(Name, D) ->
|
||||||
case maps:keys(D) -- [<<"desc">>, <<"label">>] of
|
case maps:keys(D) -- [<<"desc">>, <<"label">>] of
|
||||||
|
|
|
@ -95,9 +95,13 @@ do_merge_desc_files(BaseConf, Cfgs) ->
|
||||||
true ->
|
true ->
|
||||||
{ok, Bin1} = file:read_file(CfgFile),
|
{ok, Bin1} = file:read_file(CfgFile),
|
||||||
[Acc, io_lib:nl(), Bin1];
|
[Acc, io_lib:nl(), Bin1];
|
||||||
false -> Acc
|
false ->
|
||||||
|
Acc
|
||||||
end
|
end
|
||||||
end, BaseConf, Cfgs).
|
end,
|
||||||
|
BaseConf,
|
||||||
|
Cfgs
|
||||||
|
).
|
||||||
|
|
||||||
get_all_desc_files() ->
|
get_all_desc_files() ->
|
||||||
Dir = filename:join(["rel", "i18n"]),
|
Dir = filename:join(["rel", "i18n"]),
|
||||||
|
|
|
@ -20,8 +20,8 @@ inject_relup_file(File) ->
|
||||||
case file:script(File) of
|
case file:script(File) of
|
||||||
{ok, {CurrRelVsn, UpVsnRUs, DnVsnRUs}} ->
|
{ok, {CurrRelVsn, UpVsnRUs, DnVsnRUs}} ->
|
||||||
?INFO("Injecting instructions to: ~p", [File]),
|
?INFO("Injecting instructions to: ~p", [File]),
|
||||||
UpdatedContent = {CurrRelVsn,
|
UpdatedContent =
|
||||||
inject_relup_instrs(up, UpVsnRUs),
|
{CurrRelVsn, inject_relup_instrs(up, UpVsnRUs),
|
||||||
inject_relup_instrs(down, DnVsnRUs)},
|
inject_relup_instrs(down, DnVsnRUs)},
|
||||||
file:write_file(File, term_to_text(UpdatedContent));
|
file:write_file(File, term_to_text(UpdatedContent));
|
||||||
{ok, _BadFormat} ->
|
{ok, _BadFormat} ->
|
||||||
|
@ -36,38 +36,49 @@ inject_relup_file(File) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
inject_relup_instrs(Type, RUs) ->
|
inject_relup_instrs(Type, RUs) ->
|
||||||
lists:map(fun({Vsn, Desc, Instrs}) ->
|
lists:map(
|
||||||
|
fun({Vsn, Desc, Instrs}) ->
|
||||||
{Vsn, Desc, append_emqx_relup_instrs(Type, Vsn, Instrs)}
|
{Vsn, Desc, append_emqx_relup_instrs(Type, Vsn, Instrs)}
|
||||||
end, RUs).
|
end,
|
||||||
|
RUs
|
||||||
|
).
|
||||||
|
|
||||||
append_emqx_relup_instrs(up, FromRelVsn, Instrs0) ->
|
append_emqx_relup_instrs(up, FromRelVsn, Instrs0) ->
|
||||||
{{UpExtra, _}, Instrs1} = filter_and_check_instrs(up, Instrs0),
|
{{UpExtra, _}, Instrs1} = filter_and_check_instrs(up, Instrs0),
|
||||||
Instrs1 ++
|
Instrs1 ++
|
||||||
[ {load, {emqx_release, brutal_purge, soft_purge}}
|
[
|
||||||
, {load, {emqx_relup, brutal_purge, soft_purge}}
|
{load, {emqx_release, brutal_purge, soft_purge}},
|
||||||
, {apply, {emqx_relup, post_release_upgrade, [FromRelVsn, UpExtra]}}
|
{load, {emqx_relup, brutal_purge, soft_purge}},
|
||||||
|
{apply, {emqx_relup, post_release_upgrade, [FromRelVsn, UpExtra]}}
|
||||||
];
|
];
|
||||||
|
|
||||||
append_emqx_relup_instrs(down, ToRelVsn, Instrs0) ->
|
append_emqx_relup_instrs(down, ToRelVsn, Instrs0) ->
|
||||||
{{_, DnExtra}, Instrs1} = filter_and_check_instrs(down, Instrs0),
|
{{_, DnExtra}, Instrs1} = filter_and_check_instrs(down, Instrs0),
|
||||||
%% NOTE: When downgrading, we apply emqx_relup:post_release_downgrade/2 before reloading
|
%% NOTE: When downgrading, we apply emqx_relup:post_release_downgrade/2 before reloading
|
||||||
%% or removing the emqx_relup module.
|
%% or removing the emqx_relup module.
|
||||||
Instrs2 = Instrs1 ++
|
Instrs2 =
|
||||||
[ {load, {emqx_release, brutal_purge, soft_purge}}
|
Instrs1 ++
|
||||||
, {apply, {emqx_relup, post_release_downgrade, [ToRelVsn, DnExtra]}}
|
[
|
||||||
, {load, {emqx_relup, brutal_purge, soft_purge}}
|
{load, {emqx_release, brutal_purge, soft_purge}},
|
||||||
|
{apply, {emqx_relup, post_release_downgrade, [ToRelVsn, DnExtra]}},
|
||||||
|
{load, {emqx_relup, brutal_purge, soft_purge}}
|
||||||
],
|
],
|
||||||
Instrs2.
|
Instrs2.
|
||||||
|
|
||||||
filter_and_check_instrs(Type, Instrs) ->
|
filter_and_check_instrs(Type, Instrs) ->
|
||||||
case filter_fetch_emqx_mods_and_extra(Instrs) of
|
case filter_fetch_emqx_mods_and_extra(Instrs) of
|
||||||
{_, DnExtra, _, _} when Type =:= up, DnExtra =/= undefined ->
|
{_, DnExtra, _, _} when Type =:= up, DnExtra =/= undefined ->
|
||||||
?ERROR("Got '{apply,{emqx_relup,post_release_downgrade,[_,Extra]}}'"
|
?ERROR(
|
||||||
" from the upgrade instruction list, should be 'post_release_upgrade'", []),
|
"Got '{apply,{emqx_relup,post_release_downgrade,[_,Extra]}}'"
|
||||||
|
" from the upgrade instruction list, should be 'post_release_upgrade'",
|
||||||
|
[]
|
||||||
|
),
|
||||||
error({instruction_not_found, load_object_code});
|
error({instruction_not_found, load_object_code});
|
||||||
{UpExtra, _, _, _} when Type =:= down, UpExtra =/= undefined ->
|
{UpExtra, _, _, _} when Type =:= down, UpExtra =/= undefined ->
|
||||||
?ERROR("Got '{apply,{emqx_relup,post_release_upgrade,[_,Extra]}}'"
|
?ERROR(
|
||||||
" from the downgrade instruction list, should be 'post_release_downgrade'", []),
|
"Got '{apply,{emqx_relup,post_release_upgrade,[_,Extra]}}'"
|
||||||
|
" from the downgrade instruction list, should be 'post_release_downgrade'",
|
||||||
|
[]
|
||||||
|
),
|
||||||
error({instruction_not_found, load_object_code});
|
error({instruction_not_found, load_object_code});
|
||||||
{_, _, [], _} ->
|
{_, _, [], _} ->
|
||||||
?ERROR("Cannot find any 'load_object_code' instructions for app emqx", []),
|
?ERROR("Cannot find any 'load_object_code' instructions for app emqx", []),
|
||||||
|
@ -81,12 +92,15 @@ filter_fetch_emqx_mods_and_extra(Instrs) ->
|
||||||
lists:foldl(fun do_filter_and_get/2, {undefined, undefined, [], []}, Instrs).
|
lists:foldl(fun do_filter_and_get/2, {undefined, undefined, [], []}, Instrs).
|
||||||
|
|
||||||
%% collect modules for emqx app
|
%% collect modules for emqx app
|
||||||
do_filter_and_get({load_object_code, {emqx, _AppVsn, Mods}} = Instr,
|
do_filter_and_get(
|
||||||
{UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
{load_object_code, {emqx, _AppVsn, Mods}} = Instr,
|
||||||
|
{UpExtra, DnExtra, EmqxMods, RemainInstrs}
|
||||||
|
) ->
|
||||||
{UpExtra, DnExtra, EmqxMods ++ Mods, RemainInstrs ++ [Instr]};
|
{UpExtra, DnExtra, EmqxMods ++ Mods, RemainInstrs ++ [Instr]};
|
||||||
%% remove 'load' instrs for emqx_relup and emqx_release
|
%% remove 'load' instrs for emqx_relup and emqx_release
|
||||||
do_filter_and_get({load, {Mod, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs})
|
do_filter_and_get({load, {Mod, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) when
|
||||||
when Mod =:= emqx_relup; Mod =:= emqx_release ->
|
Mod =:= emqx_relup; Mod =:= emqx_release
|
||||||
|
->
|
||||||
{UpExtra, DnExtra, EmqxMods, RemainInstrs};
|
{UpExtra, DnExtra, EmqxMods, RemainInstrs};
|
||||||
%% remove 'remove' and 'purge' instrs for emqx_relup
|
%% remove 'remove' and 'purge' instrs for emqx_relup
|
||||||
do_filter_and_get({remove, {emqx_relup, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
do_filter_and_get({remove, {emqx_relup, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
||||||
|
@ -94,22 +108,31 @@ do_filter_and_get({remove, {emqx_relup, _, _}}, {UpExtra, DnExtra, EmqxMods, Rem
|
||||||
do_filter_and_get({purge, [emqx_relup]}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
do_filter_and_get({purge, [emqx_relup]}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
||||||
{UpExtra, DnExtra, EmqxMods, RemainInstrs};
|
{UpExtra, DnExtra, EmqxMods, RemainInstrs};
|
||||||
%% remove 'apply' instrs for upgrade, and collect the 'Extra' parameter
|
%% remove 'apply' instrs for upgrade, and collect the 'Extra' parameter
|
||||||
do_filter_and_get({apply, {emqx_relup, post_release_upgrade, [_, UpExtra0]}},
|
do_filter_and_get(
|
||||||
{_, DnExtra, EmqxMods, RemainInstrs}) ->
|
{apply, {emqx_relup, post_release_upgrade, [_, UpExtra0]}},
|
||||||
|
{_, DnExtra, EmqxMods, RemainInstrs}
|
||||||
|
) ->
|
||||||
{UpExtra0, DnExtra, EmqxMods, RemainInstrs};
|
{UpExtra0, DnExtra, EmqxMods, RemainInstrs};
|
||||||
%% remove 'apply' instrs for downgrade, and collect the 'Extra' parameter
|
%% remove 'apply' instrs for downgrade, and collect the 'Extra' parameter
|
||||||
do_filter_and_get({apply, {emqx_relup, post_release_downgrade, [_, DnExtra0]}},
|
do_filter_and_get(
|
||||||
{UpExtra, _, EmqxMods, RemainInstrs}) ->
|
{apply, {emqx_relup, post_release_downgrade, [_, DnExtra0]}},
|
||||||
|
{UpExtra, _, EmqxMods, RemainInstrs}
|
||||||
|
) ->
|
||||||
{UpExtra, DnExtra0, EmqxMods, RemainInstrs};
|
{UpExtra, DnExtra0, EmqxMods, RemainInstrs};
|
||||||
%% keep all other instrs unchanged
|
%% keep all other instrs unchanged
|
||||||
do_filter_and_get(Instr, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
do_filter_and_get(Instr, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
|
||||||
{UpExtra, DnExtra, EmqxMods, RemainInstrs ++ [Instr]}.
|
{UpExtra, DnExtra, EmqxMods, RemainInstrs ++ [Instr]}.
|
||||||
|
|
||||||
assert_mandatory_modules(_, Mods) ->
|
assert_mandatory_modules(_, Mods) ->
|
||||||
MandInstrs = [{load_module,emqx_release,brutal_purge,soft_purge,[]},
|
MandInstrs = [
|
||||||
{load_module,emqx_relup}],
|
{load_module, emqx_release, brutal_purge, soft_purge, []},
|
||||||
assert(lists:member(emqx_relup, Mods) andalso lists:member(emqx_release, Mods),
|
{load_module, emqx_relup}
|
||||||
"The following instructions are mandatory in every clause of the emqx.appup.src: ~p", [MandInstrs]).
|
],
|
||||||
|
assert(
|
||||||
|
lists:member(emqx_relup, Mods) andalso lists:member(emqx_release, Mods),
|
||||||
|
"The following instructions are mandatory in every clause of the emqx.appup.src: ~p",
|
||||||
|
[MandInstrs]
|
||||||
|
).
|
||||||
|
|
||||||
assert(true, _, _) ->
|
assert(true, _, _) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env -S escript -c
|
#!/usr/bin/env -S escript -c
|
||||||
%% -*- erlang-indent-level:4 -*-
|
%% -*- erlang-indent-level:4 -*-
|
||||||
|
|
||||||
|
%% erlfmt-ignore
|
||||||
usage() ->
|
usage() ->
|
||||||
"A script that fills in boilerplate for appup files.
|
"A script that fills in boilerplate for appup files.
|
||||||
|
|
||||||
|
@ -35,19 +36,20 @@ Options:
|
||||||
--src-dirs Directories where source code is found. Defaults to '{src,apps}/**/'
|
--src-dirs Directories where source code is found. Defaults to '{src,apps}/**/'
|
||||||
".
|
".
|
||||||
|
|
||||||
-record(app,
|
-record(app, {
|
||||||
{ modules :: #{module() => binary()}
|
modules :: #{module() => binary()},
|
||||||
, version :: string()
|
version :: string()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
default_options() ->
|
default_options() ->
|
||||||
#{ clone_url => find_upstream_repo("origin")
|
#{
|
||||||
, make_command => "make emqx-rel"
|
clone_url => find_upstream_repo("origin"),
|
||||||
, beams_dir => "_build/emqx/rel/emqx/lib/"
|
make_command => "make emqx-rel",
|
||||||
, check => false
|
beams_dir => "_build/emqx/rel/emqx/lib/",
|
||||||
, prev_tag => undefined
|
check => false,
|
||||||
, src_dirs => "{src,apps}/**/"
|
prev_tag => undefined,
|
||||||
, prev_beams_dir => undefined
|
src_dirs => "{src,apps}/**/",
|
||||||
|
prev_beams_dir => undefined
|
||||||
}.
|
}.
|
||||||
|
|
||||||
%% App-specific actions that should be added unconditionally to any update/downgrade:
|
%% App-specific actions that should be added unconditionally to any update/downgrade:
|
||||||
|
@ -55,31 +57,31 @@ app_specific_actions(_) ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
ignored_apps() ->
|
ignored_apps() ->
|
||||||
[gpb %% only a build tool
|
%% only a build tool
|
||||||
] ++ otp_standard_apps().
|
[gpb] ++ otp_standard_apps().
|
||||||
|
|
||||||
main(Args) ->
|
main(Args) ->
|
||||||
#{prev_tag := Baseline} = Options = parse_args(Args, default_options()),
|
#{prev_tag := Baseline} = Options = parse_args(Args, default_options()),
|
||||||
init_globals(Options),
|
init_globals(Options),
|
||||||
main(Options, Baseline).
|
main(Options, Baseline).
|
||||||
|
|
||||||
parse_args([PrevTag = [A|_]], State) when A =/= $- ->
|
parse_args([PrevTag = [A | _]], State) when A =/= $- ->
|
||||||
State#{prev_tag => PrevTag};
|
State#{prev_tag => PrevTag};
|
||||||
parse_args(["--check"|Rest], State) ->
|
parse_args(["--check" | Rest], State) ->
|
||||||
parse_args(Rest, State#{check => true});
|
parse_args(Rest, State#{check => true});
|
||||||
parse_args(["--skip-build"|Rest], State) ->
|
parse_args(["--skip-build" | Rest], State) ->
|
||||||
parse_args(Rest, State#{make_command => undefined});
|
parse_args(Rest, State#{make_command => undefined});
|
||||||
parse_args(["--repo", Repo|Rest], State) ->
|
parse_args(["--repo", Repo | Rest], State) ->
|
||||||
parse_args(Rest, State#{clone_url => Repo});
|
parse_args(Rest, State#{clone_url => Repo});
|
||||||
parse_args(["--remote", Remote|Rest], State) ->
|
parse_args(["--remote", Remote | Rest], State) ->
|
||||||
parse_args(Rest, State#{clone_url => find_upstream_repo(Remote)});
|
parse_args(Rest, State#{clone_url => find_upstream_repo(Remote)});
|
||||||
parse_args(["--make-command", Command|Rest], State) ->
|
parse_args(["--make-command", Command | Rest], State) ->
|
||||||
parse_args(Rest, State#{make_command => Command});
|
parse_args(Rest, State#{make_command => Command});
|
||||||
parse_args(["--release-dir", Dir|Rest], State) ->
|
parse_args(["--release-dir", Dir | Rest], State) ->
|
||||||
parse_args(Rest, State#{beams_dir => Dir});
|
parse_args(Rest, State#{beams_dir => Dir});
|
||||||
parse_args(["--prev-release-dir", Dir|Rest], State) ->
|
parse_args(["--prev-release-dir", Dir | Rest], State) ->
|
||||||
parse_args(Rest, State#{prev_beams_dir => Dir});
|
parse_args(Rest, State#{prev_beams_dir => Dir});
|
||||||
parse_args(["--src-dirs", Pattern|Rest], State) ->
|
parse_args(["--src-dirs", Pattern | Rest], State) ->
|
||||||
parse_args(Rest, State#{src_dirs => Pattern});
|
parse_args(Rest, State#{src_dirs => Pattern});
|
||||||
parse_args(_, _) ->
|
parse_args(_, _) ->
|
||||||
fail(usage()).
|
fail(usage()).
|
||||||
|
@ -87,9 +89,11 @@ parse_args(_, _) ->
|
||||||
main(Options, Baseline) ->
|
main(Options, Baseline) ->
|
||||||
{CurrRelDir, PrevRelDir} = prepare(Baseline, Options),
|
{CurrRelDir, PrevRelDir} = prepare(Baseline, Options),
|
||||||
putopt(prev_beams_dir, PrevRelDir),
|
putopt(prev_beams_dir, PrevRelDir),
|
||||||
log("~n===================================~n"
|
log(
|
||||||
|
"~n===================================~n"
|
||||||
"Processing changes..."
|
"Processing changes..."
|
||||||
"~n===================================~n"),
|
"~n===================================~n"
|
||||||
|
),
|
||||||
CurrAppsIdx = index_apps(CurrRelDir),
|
CurrAppsIdx = index_apps(CurrRelDir),
|
||||||
PrevAppsIdx = index_apps(PrevRelDir),
|
PrevAppsIdx = index_apps(PrevRelDir),
|
||||||
%% log("Curr: ~p~nPrev: ~p~n", [CurrAppsIdx, PrevAppsIdx]),
|
%% log("Curr: ~p~nPrev: ~p~n", [CurrAppsIdx, PrevAppsIdx]),
|
||||||
|
@ -98,6 +102,7 @@ main(Options, Baseline) ->
|
||||||
ok = check_appup_files(),
|
ok = check_appup_files(),
|
||||||
ok = warn_and_exit(is_valid()).
|
ok = warn_and_exit(is_valid()).
|
||||||
|
|
||||||
|
%% erlfmt-ignore
|
||||||
warn_and_exit(true) ->
|
warn_and_exit(true) ->
|
||||||
log("
|
log("
|
||||||
NOTE: Please review the changes manually. This script does not know about NIF
|
NOTE: Please review the changes manually. This script does not know about NIF
|
||||||
|
@ -109,9 +114,12 @@ warn_and_exit(false) ->
|
||||||
halt(1).
|
halt(1).
|
||||||
|
|
||||||
prepare(Baseline, Options = #{make_command := MakeCommand, beams_dir := BeamDir}) ->
|
prepare(Baseline, Options = #{make_command := MakeCommand, beams_dir := BeamDir}) ->
|
||||||
log("~n===================================~n"
|
log(
|
||||||
|
"~n===================================~n"
|
||||||
"Baseline: ~s"
|
"Baseline: ~s"
|
||||||
"~n===================================~n", [Baseline]),
|
"~n===================================~n",
|
||||||
|
[Baseline]
|
||||||
|
),
|
||||||
log("Building the current version...~n"),
|
log("Building the current version...~n"),
|
||||||
ok = bash(MakeCommand),
|
ok = bash(MakeCommand),
|
||||||
PrevRelDir =
|
PrevRelDir =
|
||||||
|
@ -126,6 +134,7 @@ prepare(Baseline, Options = #{make_command := MakeCommand, beams_dir := BeamDir}
|
||||||
end,
|
end,
|
||||||
{BeamDir, PrevRelDir}.
|
{BeamDir, PrevRelDir}.
|
||||||
|
|
||||||
|
%% erlfmt-ignore
|
||||||
build_prev_release(Baseline, #{clone_url := Repo, make_command := MakeCommand}) ->
|
build_prev_release(Baseline, #{clone_url := Repo, make_command := MakeCommand}) ->
|
||||||
BaseDir = "/tmp/emqx-appup-base/",
|
BaseDir = "/tmp/emqx-appup-base/",
|
||||||
Dir = filename:basename(Repo, ".git") ++ [$-|Baseline],
|
Dir = filename:basename(Repo, ".git") ++ [$-|Baseline],
|
||||||
|
@ -156,14 +165,17 @@ find_appup_actions(CurrApps, PrevApps) ->
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
[],
|
[],
|
||||||
CurrApps).
|
CurrApps
|
||||||
|
).
|
||||||
|
|
||||||
find_appup_actions(_App, AppIdx, AppIdx) ->
|
find_appup_actions(_App, AppIdx, AppIdx) ->
|
||||||
%% No changes to the app, ignore:
|
%% No changes to the app, ignore:
|
||||||
[];
|
[];
|
||||||
find_appup_actions(App,
|
find_appup_actions(
|
||||||
|
App,
|
||||||
CurrAppIdx = #app{version = CurrVersion},
|
CurrAppIdx = #app{version = CurrVersion},
|
||||||
PrevAppIdx = #app{version = PrevVersion}) ->
|
PrevAppIdx = #app{version = PrevVersion}
|
||||||
|
) ->
|
||||||
{OldUpgrade0, OldDowngrade0} = find_base_appup_actions(App, PrevVersion),
|
{OldUpgrade0, OldDowngrade0} = find_base_appup_actions(App, PrevVersion),
|
||||||
OldUpgrade = ensure_all_patch_versions(App, CurrVersion, OldUpgrade0),
|
OldUpgrade = ensure_all_patch_versions(App, CurrVersion, OldUpgrade0),
|
||||||
OldDowngrade = ensure_all_patch_versions(App, CurrVersion, OldDowngrade0),
|
OldDowngrade = ensure_all_patch_versions(App, CurrVersion, OldDowngrade0),
|
||||||
|
@ -195,7 +207,10 @@ do_ensure_all_patch_versions(App, CurrVsn, OldActions) ->
|
||||||
{ok, ExpectedVsns} ->
|
{ok, ExpectedVsns} ->
|
||||||
CoveredVsns = [V || {V, _} <- OldActions, V =/= <<".*">>],
|
CoveredVsns = [V || {V, _} <- OldActions, V =/= <<".*">>],
|
||||||
ExpectedVsnStrs = [vsn_number_to_string(V) || V <- ExpectedVsns],
|
ExpectedVsnStrs = [vsn_number_to_string(V) || V <- ExpectedVsns],
|
||||||
MissingActions = [{V, []} || V <- ExpectedVsnStrs, not contains_version(V, CoveredVsns)],
|
MissingActions = [
|
||||||
|
{V, []}
|
||||||
|
|| V <- ExpectedVsnStrs, not contains_version(V, CoveredVsns)
|
||||||
|
],
|
||||||
MissingActions ++ OldActions;
|
MissingActions ++ OldActions;
|
||||||
{error, bad_version} ->
|
{error, bad_version} ->
|
||||||
log("WARN: Could not infer expected versions to upgrade from for ~p~n", [App]),
|
log("WARN: Could not infer expected versions to upgrade from for ~p~n", [App]),
|
||||||
|
@ -222,7 +237,8 @@ diff_appup_instructions(ComputedChanges, PresentChanges) ->
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
[],
|
[],
|
||||||
ComputedChanges).
|
ComputedChanges
|
||||||
|
).
|
||||||
|
|
||||||
%% checks if any missing diffs are present
|
%% checks if any missing diffs are present
|
||||||
%% and groups them by `up' and `down' types.
|
%% and groups them by `up' and `down' types.
|
||||||
|
@ -234,8 +250,9 @@ parse_appup_diffs(Upgrade, OldUpgrade, Downgrade, OldDowngrade) ->
|
||||||
%% no diff for external dependency; ignore
|
%% no diff for external dependency; ignore
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
Diffs = #{ up => DiffUp
|
Diffs = #{
|
||||||
, down => DiffDown
|
up => DiffUp,
|
||||||
|
down => DiffDown
|
||||||
},
|
},
|
||||||
{diffs, Diffs}
|
{diffs, Diffs}
|
||||||
end.
|
end.
|
||||||
|
@ -260,7 +277,9 @@ find_base_appup_actions(App, PrevVersion) ->
|
||||||
{ensure_version(PrevVersion, Upgrade), ensure_version(PrevVersion, Downgrade)}.
|
{ensure_version(PrevVersion, Upgrade), ensure_version(PrevVersion, Downgrade)}.
|
||||||
|
|
||||||
merge_update_actions(App, Changes, Vsns, PrevVersion) ->
|
merge_update_actions(App, Changes, Vsns, PrevVersion) ->
|
||||||
lists:map(fun(Ret = {<<".*">>, _}) ->
|
lists:map(
|
||||||
|
fun
|
||||||
|
(Ret = {<<".*">>, _}) ->
|
||||||
Ret;
|
Ret;
|
||||||
({Vsn, Actions}) ->
|
({Vsn, Actions}) ->
|
||||||
case is_skipped_version(App, Vsn, PrevVersion) of
|
case is_skipped_version(App, Vsn, PrevVersion) of
|
||||||
|
@ -271,7 +290,8 @@ merge_update_actions(App, Changes, Vsns, PrevVersion) ->
|
||||||
{Vsn, do_merge_update_actions(App, Changes, Actions)}
|
{Vsn, do_merge_update_actions(App, Changes, Actions)}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
Vsns).
|
Vsns
|
||||||
|
).
|
||||||
|
|
||||||
%% say current version is 1.1.3, and the compare base is version 1.1.1,
|
%% say current version is 1.1.3, and the compare base is version 1.1.1,
|
||||||
%% but there is a 1.1.2 in appup we may skip merging instructions for
|
%% but there is a 1.1.2 in appup we may skip merging instructions for
|
||||||
|
@ -331,11 +351,14 @@ contains_restart_application(Application, Actions) ->
|
||||||
find_application_stop_instruction(Application, Actions) ->
|
find_application_stop_instruction(Application, Actions) ->
|
||||||
{Before, After0} =
|
{Before, After0} =
|
||||||
lists:splitwith(
|
lists:splitwith(
|
||||||
fun({apply, {application, stop, [App]}}) when App =:= Application ->
|
fun
|
||||||
|
({apply, {application, stop, [App]}}) when App =:= Application ->
|
||||||
false;
|
false;
|
||||||
(_) ->
|
(_) ->
|
||||||
true
|
true
|
||||||
end, Actions),
|
end,
|
||||||
|
Actions
|
||||||
|
),
|
||||||
case After0 of
|
case After0 of
|
||||||
[StopInst | After] ->
|
[StopInst | After] ->
|
||||||
{Before ++ [StopInst], After};
|
{Before ++ [StopInst], After};
|
||||||
|
@ -353,8 +376,10 @@ process_old_action({delete_module, Module}) ->
|
||||||
[Module];
|
[Module];
|
||||||
process_old_action({update, Module, _Change}) ->
|
process_old_action({update, Module, _Change}) ->
|
||||||
[Module];
|
[Module];
|
||||||
process_old_action(LoadModule) when is_tuple(LoadModule) andalso
|
process_old_action(LoadModule) when
|
||||||
element(1, LoadModule) =:= load_module ->
|
is_tuple(LoadModule) andalso
|
||||||
|
element(1, LoadModule) =:= load_module
|
||||||
|
->
|
||||||
element(2, LoadModule);
|
element(2, LoadModule);
|
||||||
process_old_action(_) ->
|
process_old_action(_) ->
|
||||||
[].
|
[].
|
||||||
|
@ -370,7 +395,8 @@ ensure_version(Version, OldInstructions) ->
|
||||||
|
|
||||||
contains_version(Needle, Haystack) when is_list(Needle) ->
|
contains_version(Needle, Haystack) when is_list(Needle) ->
|
||||||
lists:any(
|
lists:any(
|
||||||
fun(Regex) when is_binary(Regex) ->
|
fun
|
||||||
|
(Regex) when is_binary(Regex) ->
|
||||||
case re:run(Needle, Regex) of
|
case re:run(Needle, Regex) of
|
||||||
{match, _} ->
|
{match, _} ->
|
||||||
true;
|
true;
|
||||||
|
@ -380,7 +406,8 @@ contains_version(Needle, Haystack) when is_list(Needle) ->
|
||||||
(Vsn) ->
|
(Vsn) ->
|
||||||
Vsn =:= Needle
|
Vsn =:= Needle
|
||||||
end,
|
end,
|
||||||
Haystack).
|
Haystack
|
||||||
|
).
|
||||||
|
|
||||||
%% As a best effort approach, we assume that we only bump patch
|
%% As a best effort approach, we assume that we only bump patch
|
||||||
%% version numbers between release upgrades for our dependencies and
|
%% version numbers between release upgrades for our dependencies and
|
||||||
|
@ -437,7 +464,8 @@ update_appups(Changes) ->
|
||||||
fun({App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}) ->
|
fun({App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}) ->
|
||||||
do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade)
|
do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade)
|
||||||
end,
|
end,
|
||||||
Changes).
|
Changes
|
||||||
|
).
|
||||||
|
|
||||||
do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade) ->
|
do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade) ->
|
||||||
case locate_current_src(App, ".appup.src") of
|
case locate_current_src(App, ".appup.src") of
|
||||||
|
@ -469,8 +497,11 @@ check_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade) ->
|
||||||
ok;
|
ok;
|
||||||
{diffs, Diffs} ->
|
{diffs, Diffs} ->
|
||||||
set_invalid(),
|
set_invalid(),
|
||||||
log("ERROR: Appup file for '~p' is not complete.~n"
|
log(
|
||||||
"Missing:~100p~n", [App, Diffs]),
|
"ERROR: Appup file for '~p' is not complete.~n"
|
||||||
|
"Missing:~100p~n",
|
||||||
|
[App, Diffs]
|
||||||
|
),
|
||||||
notok
|
notok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -496,9 +527,12 @@ render_appup(App, File, Up, Down) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_render_appup(File, Up, Down) ->
|
do_render_appup(File, Up, Down) ->
|
||||||
IOList = io_lib:format("%% -*- mode: erlang -*-~n"
|
IOList = io_lib:format(
|
||||||
|
"%% -*- mode: erlang -*-~n"
|
||||||
"%% Unless you know what you are doing, DO NOT edit manually!!~n"
|
"%% Unless you know what you are doing, DO NOT edit manually!!~n"
|
||||||
"{VSN,~n ~p,~n ~p}.~n", [Up, Down]),
|
"{VSN,~n ~p,~n ~p}.~n",
|
||||||
|
[Up, Down]
|
||||||
|
),
|
||||||
ok = file:write_file(File, IOList).
|
ok = file:write_file(File, IOList).
|
||||||
|
|
||||||
create_stub(App) ->
|
create_stub(App) ->
|
||||||
|
@ -544,15 +578,20 @@ index_app(AppFile) ->
|
||||||
%% Note: assuming that beams are always located in the same directory where app file is:
|
%% Note: assuming that beams are always located in the same directory where app file is:
|
||||||
EbinDir = filename:dirname(AppFile),
|
EbinDir = filename:dirname(AppFile),
|
||||||
Modules = hashsums(EbinDir),
|
Modules = hashsums(EbinDir),
|
||||||
{App, #app{ version = Vsn
|
{App, #app{
|
||||||
, modules = Modules
|
version = Vsn,
|
||||||
|
modules = Modules
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
diff_app(UpOrDown, App,
|
diff_app(
|
||||||
|
UpOrDown,
|
||||||
|
App,
|
||||||
#app{version = NewVersion, modules = NewModules},
|
#app{version = NewVersion, modules = NewModules},
|
||||||
#app{version = OldVersion, modules = OldModules}) ->
|
#app{version = OldVersion, modules = OldModules}
|
||||||
|
) ->
|
||||||
{New, Changed} =
|
{New, Changed} =
|
||||||
maps:fold( fun(Mod, MD5, {New, Changed}) ->
|
maps:fold(
|
||||||
|
fun(Mod, MD5, {New, Changed}) ->
|
||||||
case OldModules of
|
case OldModules of
|
||||||
#{Mod := OldMD5} when MD5 =:= OldMD5 ->
|
#{Mod := OldMD5} when MD5 =:= OldMD5 ->
|
||||||
{New, Changed};
|
{New, Changed};
|
||||||
|
@ -561,13 +600,15 @@ diff_app(UpOrDown, App,
|
||||||
_ ->
|
_ ->
|
||||||
{[Mod | New], Changed}
|
{[Mod | New], Changed}
|
||||||
end
|
end
|
||||||
end
|
end,
|
||||||
, {[], []}
|
{[], []},
|
||||||
, NewModules
|
NewModules
|
||||||
),
|
),
|
||||||
Deleted = maps:keys(maps:without(maps:keys(NewModules), OldModules)),
|
Deleted = maps:keys(maps:without(maps:keys(NewModules), OldModules)),
|
||||||
Changes = lists:filter(fun({_T, L}) -> length(L) > 0 end,
|
Changes = lists:filter(
|
||||||
[{added, New}, {changed, Changed}, {deleted, Deleted}]),
|
fun({_T, L}) -> length(L) > 0 end,
|
||||||
|
[{added, New}, {changed, Changed}, {deleted, Deleted}]
|
||||||
|
),
|
||||||
case NewVersion =:= OldVersion of
|
case NewVersion =:= OldVersion of
|
||||||
true when Changes =:= [] ->
|
true when Changes =:= [] ->
|
||||||
%% no change
|
%% no change
|
||||||
|
@ -577,13 +618,17 @@ diff_app(UpOrDown, App,
|
||||||
case UpOrDown =:= up of
|
case UpOrDown =:= up of
|
||||||
true ->
|
true ->
|
||||||
%% only log for the upgrade case because it would be the same result
|
%% only log for the upgrade case because it would be the same result
|
||||||
log("ERROR: Application '~p' contains changes, but its version is not updated. ~s",
|
log(
|
||||||
[App, format_changes(Changes)]);
|
"ERROR: Application '~p' contains changes, but its version is not updated. ~s",
|
||||||
|
[App, format_changes(Changes)]
|
||||||
|
);
|
||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
log("INFO: Application '~p' has been updated: ~p --[~p]--> ~p~n", [App, OldVersion, UpOrDown, NewVersion]),
|
log("INFO: Application '~p' has been updated: ~p --[~p]--> ~p~n", [
|
||||||
|
App, OldVersion, UpOrDown, NewVersion
|
||||||
|
]),
|
||||||
log("INFO: changes [~p]: ~p~n", [UpOrDown, Changes]),
|
log("INFO: changes [~p]: ~p~n", [UpOrDown, Changes]),
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
|
@ -594,14 +639,16 @@ format_changes(Changes) ->
|
||||||
|
|
||||||
-spec hashsums(file:filename()) -> #{module() => binary()}.
|
-spec hashsums(file:filename()) -> #{module() => binary()}.
|
||||||
hashsums(EbinDir) ->
|
hashsums(EbinDir) ->
|
||||||
maps:from_list(lists:map(
|
maps:from_list(
|
||||||
|
lists:map(
|
||||||
fun(Beam) ->
|
fun(Beam) ->
|
||||||
File = filename:join(EbinDir, Beam),
|
File = filename:join(EbinDir, Beam),
|
||||||
{ok, Ret = {_Module, _MD5}} = beam_lib:md5(File),
|
{ok, Ret = {_Module, _MD5}} = beam_lib:md5(File),
|
||||||
Ret
|
Ret
|
||||||
end,
|
end,
|
||||||
filelib:wildcard("*.beam", EbinDir)
|
filelib:wildcard("*.beam", EbinDir)
|
||||||
)).
|
)
|
||||||
|
).
|
||||||
|
|
||||||
is_app_external(App) ->
|
is_app_external(App) ->
|
||||||
Ext = ".app.src",
|
Ext = ".app.src",
|
||||||
|
@ -674,12 +721,13 @@ do_locate(Dir, App, Suffix) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
find_app(Pattern) ->
|
find_app(Pattern) ->
|
||||||
lists:filter(fun(D) -> re:run(D, "apps/.*/_build") =:= nomatch end,
|
lists:filter(
|
||||||
filelib:wildcard(Pattern)).
|
fun(D) -> re:run(D, "apps/.*/_build") =:= nomatch end,
|
||||||
|
filelib:wildcard(Pattern)
|
||||||
|
).
|
||||||
|
|
||||||
bash(undefined) -> ok;
|
bash(undefined) -> ok;
|
||||||
bash(Script) ->
|
bash(Script) -> bash(Script, []).
|
||||||
bash(Script, []).
|
|
||||||
|
|
||||||
bash(Script, Env) ->
|
bash(Script, Env) ->
|
||||||
log("+ ~s~n+ Env: ~p~n", [Script, Env]),
|
log("+ ~s~n+ Env: ~p~n", [Script, Env]),
|
||||||
|
@ -695,9 +743,11 @@ cmd(Exec, Params) ->
|
||||||
fail("Executable not found in $PATH: ~s", [Exec]);
|
fail("Executable not found in $PATH: ~s", [Exec]);
|
||||||
Path ->
|
Path ->
|
||||||
Params1 = maps:to_list(maps:with([env, args, cd], Params)),
|
Params1 = maps:to_list(maps:with([env, args, cd], Params)),
|
||||||
Port = erlang:open_port( {spawn_executable, Path}
|
Port = erlang:open_port(
|
||||||
, [ exit_status
|
{spawn_executable, Path},
|
||||||
, nouse_stdio
|
[
|
||||||
|
exit_status,
|
||||||
|
nouse_stdio
|
||||||
| Params1
|
| Params1
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue