mirror of
https://github.com/correl/rebar.git
synced 2024-11-23 11:09:55 +00:00
Add 'generate-appups' command
To further support OTP releases I have added support for generating application appup files. These include instructions that systools uses to generate a relup file which contains the low level instructions needed to perform a hot code upgrade. My goal with this module is to produce "good enough" appup files or at least a skeleton to help one get started with something more complex. If an appup file already exists for an application this command will not attempt to create a new one. Usage: $ rebar generate-appups previous_release=/path/to/old/version Generally this command will be run just before 'generate-upgrade'.
This commit is contained in:
parent
9ee8ed9181
commit
11bf6b4aab
6 changed files with 233 additions and 11 deletions
|
@ -3,6 +3,7 @@
|
|||
{vsn, "2"},
|
||||
{modules, [ rebar,
|
||||
rebar_abnfc_compiler,
|
||||
rebar_appups,
|
||||
rebar_app_utils,
|
||||
rebar_base_compiler,
|
||||
rebar_config,
|
||||
|
@ -80,6 +81,7 @@
|
|||
]},
|
||||
|
||||
{rel_dir, [
|
||||
rebar_appups,
|
||||
rebar_reltool,
|
||||
rebar_upgrade
|
||||
]}
|
||||
|
|
|
@ -224,6 +224,8 @@ generate [dump_spec=0/1] Build release with reltool
|
|||
|
||||
generate-upgrade previous_release=path Build an upgrade package
|
||||
|
||||
generate-appups previous_release=path Generate appup files
|
||||
|
||||
eunit [suite=foo] Run eunit [test/foo_tests.erl] tests
|
||||
ct [suite=] [case=] Run common_test suites in ./test
|
||||
|
||||
|
@ -277,8 +279,8 @@ filter_flags([Item | Rest], Commands) ->
|
|||
command_names() ->
|
||||
["build-plt", "check-deps", "check-plt", "clean", "compile", "create",
|
||||
"create-app", "create-node", "ct", "delete-deps", "dialyze", "doc",
|
||||
"eunit", "generate", "generate-upgrade", "get-deps", "help",
|
||||
"list-templates", "update-deps", "version", "xref"].
|
||||
"eunit", "generate", "generate-appups", "generate-upgrade", "get-deps",
|
||||
"help", "list-templates", "update-deps", "version", "xref"].
|
||||
|
||||
unabbreviate_command_names([]) ->
|
||||
[];
|
||||
|
|
185
src/rebar_appups.erl
Normal file
185
src/rebar_appups.erl
Normal file
|
@ -0,0 +1,185 @@
|
|||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
||||
%% ex: ts=4 sw=4 et
|
||||
%% -------------------------------------------------------------------
|
||||
%%
|
||||
%% rebar: Erlang Build Tools
|
||||
%%
|
||||
%% Copyright (c) 2011 Joe Williams (joe@joetify.com)
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in
|
||||
%% all copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
%% THE SOFTWARE.
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-module(rebar_appups).
|
||||
|
||||
-include("rebar.hrl").
|
||||
|
||||
-export(['generate-appups'/2]).
|
||||
|
||||
-define(APPUPFILEFORMAT, "%% appup generated for ~p by rebar (~p)~n"
|
||||
"{~p, [{~p, ~p}], [{~p, []}]}.~n").
|
||||
|
||||
%% ====================================================================
|
||||
%% Public API
|
||||
%% ====================================================================
|
||||
|
||||
'generate-appups'(_Config, ReltoolFile) ->
|
||||
%% Get the old release path
|
||||
OldVerPath = rebar_utils:get_previous_release_path(),
|
||||
|
||||
%% Get the new and old release name and versions
|
||||
{Name, _Ver} = rebar_utils:get_reltool_release_info(ReltoolFile),
|
||||
NewVerPath = filename:join([".", Name]),
|
||||
{NewName, NewVer} = rebar_utils:get_rel_release_info(Name, NewVerPath),
|
||||
{OldName, OldVer} = rebar_utils:get_rel_release_info(Name, OldVerPath),
|
||||
|
||||
%% Run some simple checks
|
||||
true = rebar_utils:prop_check(NewVer =/= OldVer,
|
||||
"New and old .rel versions match~n", []),
|
||||
true = rebar_utils:prop_check(
|
||||
NewName == OldName,
|
||||
"Reltool and .rel release names do not match~n", []),
|
||||
|
||||
%% Get lists of the old and new app files
|
||||
OldAppFiles = rebar_utils:find_files(
|
||||
filename:join([OldVerPath, "lib"]), "^.*.app$"),
|
||||
NewAppFiles = rebar_utils:find_files(
|
||||
filename:join([NewName, "lib"]), "^.*.app$"),
|
||||
|
||||
%% Find all the apps that have been upgraded
|
||||
UpgradedApps = get_upgraded_apps(OldAppFiles, NewAppFiles),
|
||||
|
||||
%% Get a list of any appup files that exist in the new release
|
||||
NewAppUpFiles = rebar_utils:find_files(
|
||||
filename:join([NewName, "lib"]), "^.*.appup$"),
|
||||
|
||||
%% Convert the list of appup files into app names
|
||||
AppUpApps = lists:map(fun(File) ->
|
||||
file_to_name(File)
|
||||
end, NewAppUpFiles),
|
||||
|
||||
%% Create a list of apps that don't already have appups
|
||||
Apps = genappup_which_apps(UpgradedApps, AppUpApps),
|
||||
|
||||
%% Generate appup files
|
||||
generate_appup_files(Name, OldVerPath, Apps),
|
||||
|
||||
ok.
|
||||
|
||||
%% ===================================================================
|
||||
%% Internal functions
|
||||
%% ===================================================================
|
||||
|
||||
get_upgraded_apps(OldAppFiles, NewAppFiles) ->
|
||||
OldAppsVer = [get_app_version(AppFile) || AppFile <- OldAppFiles],
|
||||
NewAppsVer = [get_app_version(AppFile) || AppFile <- NewAppFiles],
|
||||
UpgradedApps = lists:subtract(NewAppsVer, OldAppsVer),
|
||||
lists:map(
|
||||
fun({App, NewVer}) ->
|
||||
{App, OldVer} = proplists:lookup(App, OldAppsVer),
|
||||
{App, {OldVer, NewVer}}
|
||||
end,
|
||||
UpgradedApps).
|
||||
|
||||
get_app_version(File) ->
|
||||
case file:consult(File) of
|
||||
{ok,[{application, Name,[_,{vsn,Ver}|_]}]} ->
|
||||
{Name, Ver};
|
||||
_ ->
|
||||
?ABORT("Failed to parse ~s~n", [File])
|
||||
end.
|
||||
|
||||
file_to_name(File) ->
|
||||
filename:rootname(filename:basename(File)).
|
||||
|
||||
genappup_which_apps(UpgradedApps, [First|Rest]) ->
|
||||
List = proplists:delete(First, UpgradedApps),
|
||||
genappup_which_apps(List, Rest);
|
||||
genappup_which_apps(Apps, []) ->
|
||||
Apps.
|
||||
|
||||
generate_appup_files(Name, OldVerPath, [{App, {OldVer, NewVer}}|Rest]) ->
|
||||
OldEbinDir = filename:join([".", OldVerPath, "lib",
|
||||
atom_to_list(App) ++ "-" ++ OldVer, "ebin"]),
|
||||
NewEbinDir = filename:join([".", Name, "lib",
|
||||
atom_to_list(App) ++ "-" ++ NewVer, "ebin"]),
|
||||
|
||||
{AddedFiles, DeletedFiles, ChangedFiles} = beam_lib:cmp_dirs(NewEbinDir,
|
||||
OldEbinDir),
|
||||
|
||||
Added = [generate_instruction(added, File) || File <- AddedFiles],
|
||||
Deleted = [generate_instruction(deleted, File) || File <- DeletedFiles],
|
||||
Changed = [generate_instruction(changed, File) || File <- ChangedFiles],
|
||||
|
||||
Inst = lists:append([Added, Deleted, Changed]),
|
||||
|
||||
AppUpFile = filename:join([NewEbinDir, atom_to_list(App) ++ ".appup"]),
|
||||
|
||||
ok = file:write_file(AppUpFile,
|
||||
io_lib:fwrite(?APPUPFILEFORMAT,
|
||||
[App, rebar_utils:now_str(), NewVer,
|
||||
OldVer, Inst, OldVer])),
|
||||
|
||||
?CONSOLE("Generated appup for ~p~n", [App]),
|
||||
generate_appup_files(Name, OldVerPath, Rest);
|
||||
generate_appup_files(_, _, []) ->
|
||||
?CONSOLE("Appup generation complete~n", []).
|
||||
|
||||
generate_instruction(added, File) ->
|
||||
Name = list_to_atom(file_to_name(File)),
|
||||
{add_module, Name};
|
||||
generate_instruction(deleted, File) ->
|
||||
Name = list_to_atom(file_to_name(File)),
|
||||
{delete_module, Name};
|
||||
generate_instruction(changed, {File, _}) ->
|
||||
{ok, {Name, List}} = beam_lib:chunks(File, [attributes, exports]),
|
||||
Behavior = get_behavior(List),
|
||||
CodeChange = is_code_change(List),
|
||||
generate_instruction_advanced(Name, Behavior, CodeChange).
|
||||
|
||||
generate_instruction_advanced(Name, undefined, undefined) ->
|
||||
%% Not a behavior or code change, assume purely functional
|
||||
{load_module, Name};
|
||||
generate_instruction_advanced(Name, [supervisor], _) ->
|
||||
%% Supervisor
|
||||
{update, Name, supervisor};
|
||||
generate_instruction_advanced(Name, _, code_change) ->
|
||||
%% Includes code_change export
|
||||
{update, Name, {advanced, []}};
|
||||
generate_instruction_advanced(Name, _, _) ->
|
||||
%% Anything else
|
||||
{update, Name}.
|
||||
|
||||
get_behavior(List) ->
|
||||
Attributes = proplists:get_value(attributes, List),
|
||||
Behavior = case proplists:get_value(behavior, Attributes) of
|
||||
undefined ->
|
||||
proplists:get_value(behaviour, Attributes);
|
||||
Else ->
|
||||
Else
|
||||
end,
|
||||
Behavior.
|
||||
|
||||
is_code_change(List) ->
|
||||
Exports = proplists:get_value(exports, List),
|
||||
case proplists:is_defined(code_change, Exports) of
|
||||
true ->
|
||||
code_change;
|
||||
false ->
|
||||
undefined
|
||||
end.
|
|
@ -37,7 +37,11 @@
|
|||
erl_to_mod/1,
|
||||
abort/2,
|
||||
escript_foldl/3,
|
||||
find_executable/1]).
|
||||
find_executable/1,
|
||||
get_reltool_release_info/1,
|
||||
get_rel_release_info/2,
|
||||
get_previous_release_path/0,
|
||||
prop_check/3]).
|
||||
|
||||
-include("rebar.hrl").
|
||||
|
||||
|
@ -151,6 +155,42 @@ find_executable(Name) ->
|
|||
"\"" ++ filename:nativename(Path) ++ "\""
|
||||
end.
|
||||
|
||||
%% Get release name and version from a reltool.config
|
||||
get_reltool_release_info(ReltoolFile) ->
|
||||
%% expect sys to be the first proplist in reltool.config
|
||||
case file:consult(ReltoolFile) of
|
||||
{ok, [{sys, Config}| _]} ->
|
||||
%% expect the first rel in the proplist to be the one you want
|
||||
{rel, Name, Ver, _} = proplists:lookup(rel, Config),
|
||||
{Name, Ver};
|
||||
_ ->
|
||||
?ABORT("Failed to parse ~s~n", [ReltoolFile])
|
||||
end.
|
||||
|
||||
%% Get release name and version from a rel file
|
||||
get_rel_release_info(Name, Path) ->
|
||||
[RelFile] = filelib:wildcard(filename:join([Path, "releases", "*",
|
||||
Name ++ ".rel"])),
|
||||
[BinDir|_] = re:replace(RelFile, Name ++ "\\.rel", ""),
|
||||
{ok, [{release, {Name1, Ver}, _, _}]} =
|
||||
file:consult(filename:join([binary_to_list(BinDir),
|
||||
Name ++ ".rel"])),
|
||||
{Name1, Ver}.
|
||||
|
||||
%% Get the previous release path from a global variable
|
||||
get_previous_release_path() ->
|
||||
case rebar_config:get_global(previous_release, false) of
|
||||
false ->
|
||||
?ABORT("previous_release=PATH is required to "
|
||||
"create upgrade package~n", []);
|
||||
OldVerPath ->
|
||||
OldVerPath
|
||||
end.
|
||||
|
||||
%% Helper function for checking values and aborting when needed
|
||||
prop_check(true, _, _) -> true;
|
||||
prop_check(false, Msg, Args) -> ?ABORT(Msg, Args).
|
||||
|
||||
%% ====================================================================
|
||||
%% Internal functions
|
||||
%% ====================================================================
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
rebar compile
|
||||
rebar generate
|
||||
rebar generate-appups previous_release=dummy_0.1
|
||||
rebar generate-upgrade previous_release=dummy_0.1
|
||||
tar -zvtf rel/dummy_0.2.tar.gz
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{"0.2",
|
||||
[{"0.1",[
|
||||
{update, dummy_server, {advanced, [foo]}}
|
||||
]}],
|
||||
[{"0.1",[
|
||||
{update, dummy_server, {advanced, [foo]}}
|
||||
]}]
|
||||
}.
|
Loading…
Reference in a new issue