summaryrefslogtreecommitdiff
path: root/src/rebar_appups.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rebar_appups.erl')
-rw-r--r--src/rebar_appups.erl185
1 files changed, 185 insertions, 0 deletions
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.