summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ebin/rebar.app2
-rw-r--r--src/rebar.erl6
-rw-r--r--src/rebar_appups.erl185
-rw-r--r--src/rebar_utils.erl42
-rw-r--r--test/upgrade_project/README.md1
-rw-r--r--test/upgrade_project/apps/dummy/ebin/dummy.appup8
6 files changed, 233 insertions, 11 deletions
diff --git a/ebin/rebar.app b/ebin/rebar.app
index 1f7db21..a99df4e 100644
--- a/ebin/rebar.app
+++ b/ebin/rebar.app
@@ -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
]}
diff --git a/src/rebar.erl b/src/rebar.erl
index 22f2080..b83a1f7 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -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([]) ->
[];
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.
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index c8eccc4..dc7de9b 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -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
%% ====================================================================
diff --git a/test/upgrade_project/README.md b/test/upgrade_project/README.md
index cf753e8..4ab439b 100644
--- a/test/upgrade_project/README.md
+++ b/test/upgrade_project/README.md
@@ -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
diff --git a/test/upgrade_project/apps/dummy/ebin/dummy.appup b/test/upgrade_project/apps/dummy/ebin/dummy.appup
deleted file mode 100644
index e77f66f..0000000
--- a/test/upgrade_project/apps/dummy/ebin/dummy.appup
+++ /dev/null
@@ -1,8 +0,0 @@
-{"0.2",
- [{"0.1",[
- {update, dummy_server, {advanced, [foo]}}
- ]}],
- [{"0.1",[
- {update, dummy_server, {advanced, [foo]}}
- ]}]
-}.