From 11bf6b4aab33c273f0db94bded8763af68cbc167 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Thu, 10 Feb 2011 13:27:29 -0800 Subject: 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'. --- src/rebar_appups.erl | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/rebar_appups.erl (limited to 'src/rebar_appups.erl') diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl new file mode 100644 index 0000000..e7333fd --- /dev/null +++ b/src/rebar_appups.erl @@ -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. -- cgit v1.1 From c6da0b3ef668531ad31e33f52d865c4e36f47313 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Wed, 16 Feb 2011 07:48:51 -0800 Subject: Fix generate-appups regression (atom vs list) --- src/rebar_appups.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/rebar_appups.erl') diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl index e7333fd..b3a730e 100644 --- a/src/rebar_appups.erl +++ b/src/rebar_appups.erl @@ -108,7 +108,7 @@ file_to_name(File) -> filename:rootname(filename:basename(File)). genappup_which_apps(UpgradedApps, [First|Rest]) -> - List = proplists:delete(First, UpgradedApps), + List = proplists:delete(list_to_atom(First), UpgradedApps), genappup_which_apps(List, Rest); genappup_which_apps(Apps, []) -> Apps. -- cgit v1.1 From 4e8dcfbfade08b48fe38bfe041459960b0ff74c3 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Wed, 16 Feb 2011 09:46:40 -0800 Subject: Clean up rebar_appups and rebar_upgrade --- src/rebar_appups.erl | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) (limited to 'src/rebar_appups.erl') diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl index b3a730e..dbc1561 100644 --- a/src/rebar_appups.erl +++ b/src/rebar_appups.erl @@ -40,13 +40,13 @@ 'generate-appups'(_Config, ReltoolFile) -> %% Get the old release path - OldVerPath = rebar_utils:get_previous_release_path(), + OldVerPath = rebar_rel_utils:get_previous_release_path(), %% Get the new and old release name and versions - {Name, _Ver} = rebar_utils:get_reltool_release_info(ReltoolFile), + {Name, _Ver} = rebar_rel_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), + {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath), + {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), %% Run some simple checks true = rebar_utils:prop_check(NewVer =/= OldVer, @@ -86,8 +86,10 @@ %% =================================================================== get_upgraded_apps(OldAppFiles, NewAppFiles) -> - OldAppsVer = [get_app_version(AppFile) || AppFile <- OldAppFiles], - NewAppsVer = [get_app_version(AppFile) || AppFile <- NewAppFiles], + OldAppsVer = [{rebar_app_utils:app_name(AppFile), + rebar_app_utils:app_vsn(AppFile)} || AppFile <- OldAppFiles], + NewAppsVer = [{rebar_app_utils:app_name(AppFile), + rebar_app_utils:app_vsn(AppFile)} || AppFile <- NewAppFiles], UpgradedApps = lists:subtract(NewAppsVer, OldAppsVer), lists:map( fun({App, NewVer}) -> @@ -96,14 +98,6 @@ get_upgraded_apps(OldAppFiles, NewAppFiles) -> 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)). -- cgit v1.1 From 1e9b2b844b844a990269f68535f5bbf02ac522b3 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Thu, 17 Feb 2011 10:12:32 -0800 Subject: Clean up trailing whitespace --- src/rebar_appups.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/rebar_appups.erl') diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl index dbc1561..ab5af29 100644 --- a/src/rebar_appups.erl +++ b/src/rebar_appups.erl @@ -86,9 +86,9 @@ %% =================================================================== get_upgraded_apps(OldAppFiles, NewAppFiles) -> - OldAppsVer = [{rebar_app_utils:app_name(AppFile), + OldAppsVer = [{rebar_app_utils:app_name(AppFile), rebar_app_utils:app_vsn(AppFile)} || AppFile <- OldAppFiles], - NewAppsVer = [{rebar_app_utils:app_name(AppFile), + NewAppsVer = [{rebar_app_utils:app_name(AppFile), rebar_app_utils:app_vsn(AppFile)} || AppFile <- NewAppFiles], UpgradedApps = lists:subtract(NewAppsVer, OldAppsVer), lists:map( -- cgit v1.1