rebar/src/rebar_appups.erl

186 lines
7.1 KiB
Erlang
Raw Normal View History

%% -*- 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(list_to_atom(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.