Browse Source

Merge pull request #126 from ferd/upgrade-redo

Upgrade procedure, from scratch (WIP)
pull/144/head
Fred Hebert 10 years ago
parent
commit
3001618a4a
8 changed files with 690 additions and 86 deletions
  1. +96
    -63
      src/rebar_prv_install_deps.erl
  2. +1
    -1
      src/rebar_prv_lock.erl
  3. +92
    -17
      src/rebar_prv_upgrade.erl
  4. +6
    -2
      src/rebar_state.erl
  5. +3
    -1
      test/mock_git_resource.erl
  6. +0
    -1
      test/rebar_deps_SUITE.erl
  7. +17
    -1
      test/rebar_test_utils.erl
  8. +475
    -0
      test/rebar_upgrade_SUITE.erl

+ 96
- 63
src/rebar_prv_install_deps.erl View File

@ -44,6 +44,8 @@
-define(PROVIDER, install_deps).
-define(DEPS, [app_discovery]).
-define(APP_NAME_INDEX, 2).
-type src_dep() :: {atom(), {atom(), string(), string()}}
| {atom(), string(), {atom(), string(), string()}}.
-type pkg_dep() :: {atom(), binary()} | atom().
@ -132,16 +134,17 @@ handle_deps(Profile, State, Deps) ->
-spec handle_deps(atom(), rebar_state:t(), list(), list() | boolean()) ->
{ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}.
handle_deps(Profile, State, Deps, Update) when is_boolean(Update) ->
handle_deps(Profile, State, Deps, Update, []);
handle_deps(Profile, State, Deps, Upgrade) when is_boolean(Upgrade) ->
handle_deps(Profile, State, Deps, Upgrade, []);
handle_deps(Profile, State, Deps, Locks) when is_list(Locks) ->
handle_deps(Profile, State, Deps, false, Locks).
Upgrade = rebar_state:get(State, upgrade, false),
handle_deps(Profile, State, Deps, Upgrade, Locks).
-spec handle_deps(atom(), rebar_state:t(), list(), boolean() | {true, binary(), integer()}, list())
-> {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}.
handle_deps(_Profile, State, [], _, _) ->
{ok, [], State};
handle_deps(Profile, State, Deps, Update, Locks) ->
handle_deps(Profile, State, Deps, Upgrade, Locks) ->
%% Read in package index and dep graph
{Packages, Graph} = rebar_packages:get_packages(State),
%% Split source deps from pkg deps, needed to keep backwards compatibility
@ -150,7 +153,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
%% Fetch transitive src deps
{State1, SrcApps, PkgDeps1, Seen} =
update_src_deps(Profile, 0, SrcDeps, PkgDeps, [], State, Update, sets:new(), Locks),
update_src_deps(Profile, 0, SrcDeps, PkgDeps, [], State, Upgrade, sets:new(), Locks),
{Solved, State2} = case PkgDeps1 of
[] -> %% No pkg deps
@ -166,7 +169,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
[warn_skip_pkg(Pkg) || Pkg <- Discarded],
Solution
end,
update_pkg_deps(Profile, S, Packages, Update, Seen, State1)
update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State1)
end,
AllDeps = lists:ukeymerge(2
@ -181,7 +184,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
%% Internal functions
%% ===================================================================
update_pkg_deps(Profile, Pkgs, Packages, Update, Seen, State) ->
update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State) ->
%% Create app_info record for each pkg dep
DepsDir = rebar_dir:deps_dir(State),
{Solved, _, State1}
@ -190,7 +193,7 @@ update_pkg_deps(Profile, Pkgs, Packages, Update, Seen, State) ->
,Packages
,Pkg),
{SeenAcc1, StateAcc1} = maybe_lock(Profile, AppInfo, SeenAcc, StateAcc, 0),
case maybe_fetch(AppInfo, Update, SeenAcc1) of
case maybe_fetch(AppInfo, Upgrade, SeenAcc1) of
true ->
{[AppInfo | Acc], SeenAcc1, StateAcc1};
false ->
@ -236,7 +239,7 @@ package_to_app(DepsDir, Packages, {Name, Vsn}) ->
end.
-spec update_src_deps(atom(), non_neg_integer(), list(), list(), list(), rebar_state:t(), boolean(), sets:set(binary()), list()) -> {rebar_state:t(), list(), list(), sets:set(binary())}.
update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen, Locks) ->
update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, Locks) ->
case lists:foldl(fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc}) ->
%% If not seen, add to list of locks to write out
Name = rebar_app_info:name(AppInfo),
@ -249,15 +252,29 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen,
true ->
ok
end,
{SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc};
%% scan for app children here if upgrading
case Upgrade of
false ->
{SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc};
true ->
{SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, LocksAcc1} =
handle_dep(AppInfo
,SrcDepsAcc
,PkgDepsAcc
,SrcAppsAcc
,Level
,StateAcc
,LocksAcc),
{SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, SeenAcc, LocksAcc1}
end;
false ->
{SeenAcc1, StateAcc1} = maybe_lock(Profile, AppInfo, SeenAcc, StateAcc, Level),
{SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, LocksAcc1} =
case Update of
{true, UpdateName, UpdateLevel} ->
handle_update(AppInfo
,UpdateName
,UpdateLevel
case Upgrade of
true ->
%{true, UpgradeName, UpgradeLevel} ->
handle_upgrade(AppInfo
,SrcDepsAcc
,PkgDepsAcc
,SrcAppsAcc
@ -278,20 +295,18 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen,
end
end,
{[], PkgDeps, SrcApps, State, Seen, Locks},
lists:sort(SrcDeps)) of
sort_deps(SrcDeps)) of
{[], NewPkgDeps, NewSrcApps, State1, Seen1, _NewLocks} ->
{State1, NewSrcApps, NewPkgDeps, Seen1};
{NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Seen1, NewLocks} ->
update_src_deps(Profile, Level+1, NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Update, Seen1, NewLocks)
update_src_deps(Profile, Level+1, NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Upgrade, Seen1, NewLocks)
end.
handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
Name = rebar_app_info:name(AppInfo),
{_, _, _, DepLevel} = lists:keyfind(Name, 1, Locks),
case UpdateLevel < DepLevel
orelse Name =:= UpdateName of
true ->
case maybe_fetch(AppInfo, true, []) of
case lists:keyfind(Name, 1, Locks) of
false ->
case maybe_fetch(AppInfo, true, sets:new()) of
true ->
handle_dep(AppInfo
,SrcDeps
@ -302,10 +317,10 @@ handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level
,Locks);
false ->
{SrcDeps, PkgDeps, SrcApps, State, Locks}
{[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
end;
false ->
{SrcDeps, PkgDeps, SrcApps, State, Locks}
_StillLocked ->
{[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
end.
handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
@ -334,7 +349,7 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->
AppInfo1 = rebar_app_info:state(AppInfo, S3),
Deps = rebar_state:get(S3, deps, []),
%% Update lock level to be the level the dep will have in this dep tree
%% Upgrade lock level to be the level the dep will have in this dep tree
NewLocks = [{DepName, Source, LockLevel+Level} ||
{DepName, Source, LockLevel} <- rebar_state:get(S3, {locks, default}, [])],
AppInfo2 = rebar_app_info:deps(AppInfo1, rebar_state:deps_names(Deps)),
@ -343,7 +358,7 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->
-spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()},
sets:set(binary())) -> boolean().
maybe_fetch(AppInfo, Update, Seen) ->
maybe_fetch(AppInfo, Upgrade, Seen) ->
AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
Apps = rebar_app_discover:find_apps(["_checkouts"], all),
case rebar_app_utils:find(rebar_app_info:name(AppInfo), Apps) of
@ -351,46 +366,15 @@ maybe_fetch(AppInfo, Update, Seen) ->
%% Don't fetch dep if it exists in the _checkouts dir
false;
error ->
Exists = case rebar_app_utils:is_app_dir(filename:absname(AppDir)++"-*") of
{true, _} ->
true;
_ ->
case rebar_app_utils:is_app_dir(filename:absname(AppDir)) of
{true, _} ->
true;
_ ->
false
end
end,
case not Exists orelse Update of
case not app_exists(AppDir) of
true ->
?INFO("Fetching ~s", [rebar_app_info:name(AppInfo)]),
Source = rebar_app_info:source(AppInfo),
case rebar_fetch:download_source(AppDir, Source) of
{error, Reason} ->
throw(Reason);
Result ->
Result
end;
_ ->
fetch_app(AppInfo, AppDir);
false ->
case sets:is_element(rebar_app_info:name(AppInfo), Seen) of
true ->
false;
false ->
Source = rebar_app_info:source(AppInfo),
case rebar_fetch:needs_update(AppDir, Source) of
true ->
?INFO("Updating ~s", [rebar_app_info:name(AppInfo)]),
case rebar_fetch:download_source(AppDir, Source) of
{error, Reason} ->
throw(Reason);
Result ->
Result
end;
false ->
false
end
maybe_upgrade(AppInfo, AppDir, Upgrade)
end
end
end.
@ -460,6 +444,55 @@ new_dep(DepsDir, Name, Vsn, Source, State) ->
rebar_state:overrides(S, ParentOverrides++Overrides)),
rebar_app_info:source(Dep1, Source).
app_exists(AppDir) ->
case rebar_app_utils:is_app_dir(filename:absname(AppDir)++"-*") of
{true, _} ->
true;
_ ->
case rebar_app_utils:is_app_dir(filename:absname(AppDir)) of
{true, _} ->
true;
_ ->
false
end
end.
fetch_app(AppInfo, AppDir) ->
?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
Source = rebar_app_info:source(AppInfo),
case rebar_fetch:download_source(AppDir, Source) of
{error, Reason} ->
throw(Reason);
Result ->
Result
end.
maybe_upgrade(AppInfo, AppDir, false) ->
Source = rebar_app_info:source(AppInfo),
rebar_fetch:needs_update(AppDir, Source);
maybe_upgrade(AppInfo, AppDir, true) ->
Source = rebar_app_info:source(AppInfo),
case rebar_fetch:needs_update(AppDir, Source) of
true ->
?INFO("Updating ~s", [rebar_app_info:name(AppInfo)]),
case rebar_fetch:download_source(AppDir, Source) of
{error, Reason} ->
throw(Reason);
Result ->
Result
end;
false ->
false
end.
sort_deps(Deps) ->
%% We need a sort stable, based on the name. So that for multiple deps on
%% the same level with the same name, we keep the order the parents had.
%% `lists:keysort/2' is documented as stable in the stdlib.
%% The list of deps is revered when we get it. For the proper stable
%% result, re-reverse it.
lists:keysort(?APP_NAME_INDEX, lists:reverse(Deps)).
-spec parse_goal(binary(), binary()) -> pkg_dep().
parse_goal(Name, Constraint) ->
case re:run(Constraint, "([^\\d]*)(\\d.*)", [{capture, [1,2], binary}]) of

+ 1
- 1
src/rebar_prv_lock.erl View File

@ -41,7 +41,7 @@ do(State) ->
,rebar_app_info:dep_level(Dep)}
end, AllDeps),
Dir = rebar_state:dir(State),
file:write_file(filename:join(Dir, "rebar.lock"), io_lib:format("~p.~n", [Locks])),
file:write_file(filename:join(Dir, ?LOCK_FILE), io_lib:format("~p.~n", [Locks])),
{ok, State}.
-spec format_error(any()) -> iolist().

+ 92
- 17
src/rebar_prv_upgrade.erl View File

@ -13,6 +13,9 @@
-define(PROVIDER, upgrade).
-define(DEPS, []).
%% Also only upgrade top-level (0) deps. Transitive deps shouldn't be
%% upgradable -- if the user wants this, they should declare it at the
%% top level and then upgrade.
%% ===================================================================
%% Public API
@ -26,33 +29,105 @@ init(State) ->
{module, ?MODULE},
{bare, false},
{deps, ?DEPS},
{example, "rebar upgrade cowboy"},
{short_desc, "Upgrade dependency."},
{desc, ""},
{example, "rebar3 upgrade [cowboy[,ranch]]"},
{short_desc, "Upgrade dependencies."},
{desc, "Upgrade project dependecies. Mentioning no application "
"will upgrade all dependencies. To upgrade specific dependencies, "
"their names can be listed in the command."},
{opts, [
{package, undefined, undefined, string, "Package to upgrade."}
{package, undefined, undefined, string,
"List of packages to upgrade. If not specified, all dependencies are upgraded."}
]}])),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
{Args, _} = rebar_state:command_parsed_args(State),
Name = ec_cnv:to_binary(proplists:get_value(package, Args)),
Locks = rebar_state:get(State, locks, []),
Locks = rebar_state:get(State, {locks, default}, []),
Deps = rebar_state:get(State, deps),
Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>)), Locks),
case prepare_locks(Names, Deps, Locks, []) of
{error, Reason} ->
{error, Reason};
{Locks0, _Unlocks0} ->
Deps0 = top_level_deps(Deps, Locks),
State1 = rebar_state:set(State, {deps, default}, Deps0),
State2 = rebar_state:set(State1, {locks, default}, Locks0),
State3 = rebar_state:set(State2, upgrade, true),
Res = rebar_prv_install_deps:do(State3),
case Res of
{ok, State4} ->
info_useless(
[element(1,Lock) || Lock <- Locks],
[rebar_app_info:name(App) || App <- rebar_state:lock(State4)]
),
rebar_prv_lock:do(State4);
_ ->
Res
end
end.
-spec format_error(any()) -> iolist().
format_error({unknown_dependency, Name}) ->
io_lib:format("Dependency ~ts not found", [Name]);
format_error({transitive_dependency, Name}) ->
io_lib:format("Dependency ~ts is transient and cannot be safely upgraded. "
"Promote it to your top-level rebar.config file to upgrade it.",
[Name]);
format_error(Reason) ->
io_lib:format("~p", [Reason]).
parse_names(Bin, Locks) ->
case lists:usort(re:split(Bin, <<" *, *">>, [trim])) of
%% Nothing submitted, use *all* apps
[<<"">>] -> [Name || {Name, _, 0} <- Locks];
[] -> [Name || {Name, _, 0} <- Locks];
%% Regular options
Other -> Other
end.
prepare_locks([], _, Locks, Unlocks) ->
{Locks, Unlocks};
prepare_locks([Name|Names], Deps, Locks, Unlocks) ->
case lists:keyfind(Name, 1, Locks) of
{_, _, _, Level} ->
Deps = rebar_state:get(State, deps),
case lists:keyfind(binary_to_atom(Name, utf8), 1, Deps) of
{_, _, 0} = Lock ->
AtomName = binary_to_atom(Name, utf8),
case lists:keyfind(AtomName, 1, Deps) of
false ->
{error, io_lib:format("No such dependency ~s~n", [Name])};
{error, {?MODULE, {unknown_dependency, Name}}};
Dep ->
rebar_prv_install_deps:handle_deps(State, [Dep], {true, Name, Level}),
{ok, State}
Source = case Dep of
{_, Src} -> Src;
{_, _, Src} -> Src
end,
{NewLocks, NewUnlocks} = unlock_higher_than(0, Locks -- [Lock]),
prepare_locks(Names,
Deps,
NewLocks,
[{Name, Source, 0} | NewUnlocks ++ Unlocks])
end;
_ ->
{error, io_lib:format("No such dependency ~s~n", [Name])}
{_, _, Level} when Level > 0 ->
{error, {?MODULE, {transitive_dependency,Name}}};
false ->
{error, {?MODULE, {unknown_dependency,Name}}}
end.
-spec format_error(any()) -> iolist().
format_error(Reason) ->
io_lib:format("~p", [Reason]).
top_level_deps(Deps, Locks) ->
[Dep || Dep <- Deps, lists:keymember(0, 3, Locks)].
unlock_higher_than(Level, Locks) -> unlock_higher_than(Level, Locks, [], []).
unlock_higher_than(_, [], Locks, Unlocks) ->
{Locks, Unlocks};
unlock_higher_than(Level, [App = {_,_,AppLevel} | Apps], Locks, Unlocks) ->
if AppLevel > Level -> unlock_higher_than(Level, Apps, Locks, [App | Unlocks]);
AppLevel =< Level -> unlock_higher_than(Level, Apps, [App | Locks], Unlocks)
end.
info_useless(Old, New) ->
[?INFO("App ~ts is no longer needed and can be deleted.", [Name])
|| Name <- Old,
not lists:member(Name, New)],
ok.

+ 6
- 2
src/rebar_state.erl View File

@ -89,8 +89,10 @@ new(ParentState, Config, Dir) ->
Opts = ParentState#state_t.opts,
LocalOpts = case rebar_config:consult_file(filename:join(Dir, ?LOCK_FILE)) of
[D] ->
LockedDeps = [X || X <- D, element(3, X) =:= 0],
dict:from_list([{{locks, default}, LockedDeps}, {{deps, default}, D} | Config]);
%% We want the top level deps only from the lock file.
%% This ensures deterministic overrides for configs.
Deps = [X || X <- D, element(3, X) =:= 0],
dict:from_list([{{locks, default}, D}, {{deps, default}, Deps} | Config]);
_ ->
D = proplists:get_value(deps, Config, []),
dict:from_list([{{deps, default}, D} | Config])
@ -133,6 +135,8 @@ current_profiles(#state_t{current_profiles=Profiles}) ->
lock(#state_t{lock=Lock}) ->
Lock.
lock(State=#state_t{}, Apps) when is_list(Apps) ->
State#state_t{lock=Apps};
lock(State=#state_t{lock=Lock}, App) ->
State#state_t{lock=[App | Lock]}.

+ 3
- 1
test/mock_git_resource.erl View File

@ -54,11 +54,13 @@ mock_lock(_) ->
%% @doc The config passed to the `mock/2' function can specify which apps
%% should be updated on a per-name basis: `{update, ["App1", "App3"]}'.
mock_update(Opts) ->
ToUpdate = proplists:get_value(update, Opts, []),
ToUpdate = proplists:get_value(upgrade, Opts, []),
ct:pal("TOUp: ~p", [ToUpdate]),
meck:expect(
?MOD, needs_update,
fun(_Dir, {git, Url, _Ref}) ->
App = app(Url),
% ct:pal("Needed update? ~p (~p) -> ~p", [App, {Url,_Ref}, lists:member(App, ToUpdate)]),
lists:member(App, ToUpdate)
end).

+ 0
- 1
test/rebar_deps_SUITE.erl View File

@ -1,4 +1,3 @@
%%% TODO: check that warnings are appearing
-module(rebar_deps_SUITE).
-compile(export_all).
-include_lib("common_test/include/ct.hrl").

+ 17
- 1
test/rebar_test_utils.erl View File

@ -53,7 +53,9 @@ run_and_check(Config, RebarConfig, Command, Expect) ->
?assertEqual({error, Reason}, Res);
{ok, Expected} ->
{ok, _} = Res,
check_results(AppDir, Expected)
check_results(AppDir, Expected);
return ->
Res
end.
%% @doc Creates a dummy application including:
@ -101,6 +103,8 @@ create_random_vsn() ->
check_results(AppDir, Expected) ->
BuildDir = filename:join([AppDir, "_build", "lib"]),
CheckoutsDir = filename:join([AppDir, "_checkouts"]),
LockFile = filename:join([AppDir, "rebar.lock"]),
Locks = lists:flatten(rebar_config:consult_file(LockFile)),
Apps = rebar_app_discover:find_apps([AppDir]),
InvalidApps = rebar_app_discover:find_apps([AppDir], invalid),
ValidApps = rebar_app_discover:find_apps([AppDir], valid),
@ -151,6 +155,18 @@ check_results(AppDir, Expected) ->
?assertEqual(iolist_to_binary(Vsn),
iolist_to_binary(rebar_app_info:original_vsn(App)))
end
; ({lock, Name}) ->
ct:pal("Name: ~p", [Name]),
?assertNotEqual(false, lists:keyfind(iolist_to_binary(Name), 1, Locks))
; ({lock, Name, Vsn}) ->
ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]),
case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
false ->
error({lock_not_found, Name});
{_LockName, {_, _, {ref, LockVsn}}, _} ->
?assertEqual(iolist_to_binary(Vsn),
iolist_to_binary(LockVsn))
end
end, Expected).
write_src_file(Dir, Name) ->

+ 475
- 0
test/rebar_upgrade_SUITE.erl View File

@ -0,0 +1,475 @@
-module(rebar_upgrade_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
all() -> [{group, git}].%, {group, pkg}].
groups() ->
[{all, [], [top_a, top_b, top_c, top_d1, top_d2, top_e,
pair_a, pair_b, pair_ab, pair_c, pair_all,
triplet_a, triplet_b, triplet_c,
tree_a, tree_b, tree_c, tree_c2, tree_ac, tree_all,
delete_d]},
{git, [], [{group, all}]},
{pkg, [], [{group, all}]}].
init_per_suite(Config) ->
application:start(meck),
Config.
end_per_suite(_Config) ->
application:stop(meck).
init_per_group(git, Config) ->
[{deps_type, git} | Config];
init_per_group(pkg, Config) ->
[{deps_type, pkg} | Config];
init_per_group(_, Config) ->
Config.
end_per_group(_, Config) ->
Config.
init_per_testcase(Case, Config) ->
DepsType = ?config(deps_type, Config),
{Deps, UpDeps, ToUp, Expectations} = upgrades(Case),
Expanded = expand_deps(DepsType, Deps),
UpExpanded = expand_deps(DepsType, UpDeps),
[{expected, normalize_unlocks(Expectations)},
{mock, fun() -> mock_deps(DepsType, Expanded, []) end},
{mock_update, fun() -> mock_deps(DepsType, UpExpanded, ToUp) end}
| setup_project(Case, Config, Expanded, UpExpanded)].
end_per_testcase(_, Config) ->
meck:unload(),
Config.
setup_project(Case, Config0, Deps, UpDeps) ->
DepsType = ?config(deps_type, Config0),
Config = rebar_test_utils:init_rebar_state(
Config0,
atom_to_list(Case)++"_"++atom_to_list(DepsType)++"_"
),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "Root", "0.0.0", [kernel, stdlib]),
TopDeps = top_level_deps(Deps),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
[{rebarconfig, RebarConf},
{next_top_deps, top_level_deps(UpDeps)} | Config].
upgrades(top_a) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"A", [{"A","1"}, "B", "C", {"D","3"}]}};
upgrades(top_b) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"B", {error, {rebar_prv_upgrade, {transitive_dependency, <<"B">>}}}}};
upgrades(top_c) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
upgrades(top_d1) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
upgrades(top_d2) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
upgrades(top_e) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"E", {error, {rebar_prv_upgrade, {unknown_dependency, <<"E">>}}}}};
upgrades(pair_a) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
],
["A","B","C","D"],
{"A", [{"A","2"},{"C","2"},{"B","1"},{"D","1"}]}};
upgrades(pair_b) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
],
["A","B","C","D"],
{"B", [{"A","1"},{"C","1"},{"B","2"},{"D","2"}]}};
upgrades(pair_ab) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
],
["A","B","C","D"],
{"A,B", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
upgrades(pair_c) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
],
["A","B","C","D"],
{"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
upgrades(pair_all) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
],
["A","B","C","D"],
{"", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
upgrades(triplet_a) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"I",[]}]}],
[{"A", "1", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"I",[]}]}],
["A","C","E","H"],
{"A", [{"A","1"}, "D", {"E","2"},
{"B","1"}, {"F","1"}, "G",
{"C","0"}, {"H","3"}, "I"]}};
upgrades(triplet_b) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"I",[]}]}],
[{"A", "1", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"I",[]}]}],
["A","C","E","H"],
{"B", [{"A","1"}, "D", {"E","3"},
{"B","1"}, {"F","1"}, "G",
{"C","0"}, {"H","3"}, "I"]}};
upgrades(triplet_c) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"I",[]}]}],
[{"A", "1", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"I",[]}]}],
["A","C","E","H"],
{"C", [{"A","1"}, "D", {"E","3"},
{"B","1"}, {"F","1"}, "G",
{"C","1"}, {"H","4"}, "I"]}};
upgrades(tree_a) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
],
["C"],
{"A", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I","2"}]}};
upgrades(tree_b) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
],
["C"],
{"B", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I","2"}]}};
upgrades(tree_c) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
],
["C","I"],
{"C", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
upgrades(tree_c2) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[{"K",[]}]},
{"I","2",[]}]}
],
["C", "H"],
{"C", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I", "2"}, "K"]}};
upgrades(tree_ac) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
],
["C","I"],
{"C, A", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
upgrades(tree_all) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
],
["C","I"],
{"", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
upgrades(delete_d) ->
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
],
[{"A", "2", [{"B", []},
{"C", []}]}
],
["A","B", "C"],
%% upgrade vs. new tree
{"", [{"A","2"}, "B", "C"]}}.
%% TODO: add a test that verifies that unlocking files and then
%% running the upgrade code is enough to properly upgrade things.
top_level_deps([]) -> [];
top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) ->
[{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)];
top_level_deps([{{pkg, Name, Vsn, _URL}, _} | Deps]) ->
[{list_to_atom(Name), Vsn} | top_level_deps(Deps)].
mock_deps(git, Deps, Upgrades) ->
catch mock_git_resource:unmock(),
mock_git_resource:mock([{deps, flat_deps(Deps)}, {upgrade, Upgrades}]);
mock_deps(pkg, Deps, Upgrades) ->
catch mock_pkg_resource:unmock(),
mock_pkg_resource:mock([{pkgdeps, flat_pkgdeps(Deps)}, {upgrade, Upgrades}]).
flat_deps([]) -> [];
flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest]) ->
[{{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)}]
++
flat_deps(Deps)
++
flat_deps(Rest).
vsn_from_ref({git, _, {_, Vsn}}) -> Vsn;
vsn_from_ref({git, _, Vsn}) -> Vsn.
flat_pkgdeps([]) -> [];
flat_pkgdeps([{{pkg, Name, Vsn, _Url}, Deps} | Rest]) ->
[{{iolist_to_binary(Name),iolist_to_binary(Vsn)}, top_level_deps(Deps)}]
++
flat_pkgdeps(Deps)
++
flat_pkgdeps(Rest).
expand_deps(_, []) -> [];
expand_deps(git, [{Name, Deps} | Rest]) ->
Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}},
[{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
expand_deps(git, [{Name, Vsn, Deps} | Rest]) ->
Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}},
[{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
expand_deps(pkg, [{Name, Deps} | Rest]) ->
Dep = {pkg, Name, "0.0.0", "https://example.org/user/"++Name++".tar.gz"},
[{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) ->
Dep = {pkg, Name, Vsn, "https://example.org/user/"++Name++".tar.gz"},
[{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)].
normalize_unlocks({App, Locks}) ->
{iolist_to_binary(App),
normalize_unlocks_expect(Locks)};
normalize_unlocks({App, Vsn, Locks}) ->
{iolist_to_binary(App), iolist_to_binary(Vsn),
normalize_unlocks_expect(Locks)}.
normalize_unlocks_expect({error, Reason}) ->
{error, Reason};
normalize_unlocks_expect([]) ->
[];
normalize_unlocks_expect([{App,Vsn} | Rest]) ->
[{dep, App, Vsn},
{lock, App, Vsn}
| normalize_unlocks_expect(Rest)];
normalize_unlocks_expect([App | Rest]) ->
[{dep, App},
{lock, App} | normalize_unlocks_expect(Rest)].
top_a(Config) -> run(Config).
top_b(Config) -> run(Config).
top_c(Config) -> run(Config).
top_d1(Config) -> run(Config).
top_d2(Config) -> run(Config).
top_e(Config) -> run(Config).
pair_a(Config) -> run(Config).
pair_b(Config) -> run(Config).
pair_ab(Config) -> run(Config).
pair_c(Config) -> run(Config).
pair_all(Config) -> run(Config).
triplet_a(Config) -> run(Config).
triplet_b(Config) -> run(Config).
triplet_c(Config) -> run(Config).
tree_a(Config) -> run(Config).
tree_b(Config) -> run(Config).
tree_c(Config) -> run(Config).
tree_c2(Config) -> run(Config).
tree_ac(Config) -> run(Config).
tree_all(Config) -> run(Config).
delete_d(Config) ->
meck:new(rebar_log, [no_link, passthrough]),
run(Config),
Infos = [{Str, Args}
|| {_, {rebar_log, log, [info, Str, Args]}, _} <- meck:history(rebar_log)],
meck:unload(rebar_log),
?assertNotEqual([],
[1 || {"App ~ts is no longer needed and can be deleted.",
[<<"D">>]} <- Infos]).
run(Config) ->
apply(?config(mock, Config), []),
{ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
%% Install dependencies before re-mocking for an upgrade
rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}),
{App, Unlocks} = ?config(expected, Config),
ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]),
Expectation = case Unlocks of
{error, Term} -> {error, Term};
_ -> {ok, Unlocks}
end,
apply(?config(mock_update, Config), []),
NewRebarConf = rebar_test_utils:create_config(?config(apps, Config),
[{deps, ?config(next_top_deps, Config)}]),
{ok, NewRebarConfig} = file:consult(NewRebarConf),
rebar_test_utils:run_and_check(
Config, NewRebarConfig, ["upgrade", App], Expectation
).

Loading…
Cancel
Save