summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rebar_prv_install_deps.erl2
-rw-r--r--src/rebar_prv_lock.erl2
-rw-r--r--src/rebar_prv_upgrade.erl48
-rw-r--r--src/rebar_state.erl8
-rw-r--r--test/rebar_deps_SUITE.erl1
-rw-r--r--test/rebar_test_utils.erl12
-rw-r--r--test/rebar_upgrade_SUITE.erl185
7 files changed, 238 insertions, 20 deletions
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index 025d32a..49aa65c 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -287,7 +287,7 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen,
handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
Name = rebar_app_info:name(AppInfo),
- {_, _, _, DepLevel} = lists:keyfind(Name, 1, Locks),
+ {_, _, DepLevel} = lists:keyfind(Name, 1, Locks),
case UpdateLevel < DepLevel
orelse Name =:= UpdateName of
true ->
diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl
index 2f26a7f..8a69434 100644
--- a/src/rebar_prv_lock.erl
+++ b/src/rebar_prv_lock.erl
@@ -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().
diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl
index c0a56c3..4609f57 100644
--- a/src/rebar_prv_upgrade.erl
+++ b/src/rebar_prv_upgrade.erl
@@ -12,7 +12,10 @@
-include("rebar.hrl").
-define(PROVIDER, upgrade).
--define(DEPS, []).
+-define(DEPS, [lock]).
+%% 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
@@ -38,19 +41,36 @@ init(State) ->
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, []),
- 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
- false ->
- {error, io_lib:format("No such dependency ~s~n", [Name])};
- Dep ->
- rebar_prv_install_deps:handle_deps(State, [Dep], {true, Name, Level}),
- {ok, State}
- end;
- _ ->
- {error, io_lib:format("No such dependency ~s~n", [Name])}
+ Locks = rebar_state:lock(State),
+ %% TODO: optimize by running the find + unlock in one sweep
+ case find_app(Name, Locks) of
+ {AppInfo, 0} ->
+ %% Unlock the app and all those with a lock level higher than
+ %% it has
+ NewLocks = unlock_higher_than(0, Locks -- [AppInfo]),
+ {ok, rebar_state:lock(State, NewLocks)};
+ {_AppInfo, Level} when Level > 0 ->
+ {error, transitive_dependency};
+ false ->
+ {error, unknown_dependency}
+ end.
+
+find_app(_Name, []) -> false;
+find_app(Name, [App|Apps]) ->
+ case rebar_app_info:name(App) of
+ Name -> {App, rebar_app_info:dep_level(App)};
+ _ -> find_app(Name, Apps)
+ end.
+
+%% Because we operate on a lock list, removing the app from the list
+%% unlocks it.
+unlock_higher_than(_, []) -> [];
+unlock_higher_than(Level, [App | Apps]) ->
+ case rebar_app_info:dep_level(App) of
+ N when N > Level ->
+ unlock_higher_than(Level, Apps);
+ N when N =< Level ->
+ [App | unlock_higher_than(Level, Apps)]
end.
-spec format_error(any()) -> iolist().
diff --git a/src/rebar_state.erl b/src/rebar_state.erl
index 29b7c3f..70aba51 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -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]}.
diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl
index d67efe4..8407b55 100644
--- a/test/rebar_deps_SUITE.erl
+++ b/test/rebar_deps_SUITE.erl
@@ -1,4 +1,3 @@
-%%% TODO: check that warnings are appearing
-module(rebar_deps_SUITE).
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl
index 6095d6d..af40995 100644
--- a/test/rebar_test_utils.erl
+++ b/test/rebar_test_utils.erl
@@ -53,7 +53,17 @@ run_and_check(Config, RebarConfig, Command, Expect) ->
?assertEqual({error, Reason}, Res);
{ok, Expected} ->
{ok, _} = Res,
- check_results(AppDir, Expected)
+ check_results(AppDir, Expected);
+ {unlocked, Expected} ->
+ ct:pal("Expected: ~p", [Expected]),
+ {ok, ResState} = Res,
+ Locks = rebar_state:lock(ResState),
+ ct:pal("Locks: ~p", [Locks]),
+ ?assertEqual(lists:sort(Expected),
+ lists:sort([rebar_app_info:name(Lock)
+ || Lock <- Locks]));
+ return ->
+ Res
end.
%% @doc Creates a dummy application including:
diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl
new file mode 100644
index 0000000..5915d0d
--- /dev/null
+++ b/test/rebar_upgrade_SUITE.erl
@@ -0,0 +1,185 @@
+-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, pair, triplet, tree]},
+ {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, Unlocks} = upgrades(Case),
+ Expanded = expand_deps(DepsType, Deps),
+ [{unlocks, normalize_unlocks(Unlocks)},
+ {mock, fun() -> mock_deps(DepsType, Expanded, []) end}
+ | setup_project(Case, Config, Expanded)].
+
+end_per_testcase(_, Config) ->
+ meck:unload(),
+ Config.
+
+setup_project(Case, Config0, Deps) ->
+ 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} | Config].
+
+
+upgrades(top) ->
+ {[{"A", [{"B", [{"D", "1", []}]},
+ {"C", [{"D", "2", []}]}]}
+ ],
+ %% upgrade vs. locked after upgrade
+ [{"A", []},
+ {"B", {error, transitive_dependency}},
+ {"C", {error, transitive_dependency}},
+ {"D", "1", {error, transitive_dependency}},
+ {"D", "2", {error, unknown_dependency}}]};
+upgrades(pair) ->
+ {[{"A", [{"C", []}]},
+ {"B", [{"D", []}]}],
+ [{"A", ["B"]},
+ {"B", ["A"]},
+ {"C", {error, transitive_dependency}},
+ {"D", {error, transitive_dependency}}]};
+upgrades(triplet) ->
+ {[{"A", [{"D",[]},
+ {"E",[]}]},
+ {"B", [{"F",[]},
+ {"G",[]}]},
+ {"C", [{"H",[]},
+ {"I",[]}]}],
+ [{"A", ["B","C"]},
+ {"B", ["A","C"]},
+ {"C", ["A","B"]},
+ {"D", {error, transitive_dependency}},
+ %% ...
+ {"I", {error, transitive_dependency}}]};
+upgrades(tree) ->
+ {[{"A", [{"D",[{"J",[]}]},
+ {"E",[{"K",[]}]}]},
+ {"B", [{"F",[]},
+ {"G",[]}]},
+ {"C", [{"H",[]},
+ {"I",[]}]}],
+ [{"A", ["B","C"]},
+ {"B", ["A","C"]},
+ {"C", ["A","B"]},
+ {"D", {error, transitive_dependency}},
+ %% ...
+ {"K", {error, transitive_dependency}}]}.
+
+%% 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) ->
+ mock_git_resource:mock([{deps, flat_deps(Deps)}, {upgrade, Upgrades}]);
+mock_deps(pkg, Deps, Upgrades) ->
+ 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([]) -> [];
+normalize_unlocks([{App, Locks} | Rest]) ->
+ [{iolist_to_binary(App),
+ normalize_unlocks_expect(Locks)} | normalize_unlocks(Rest)];
+normalize_unlocks([{App, Vsn, Locks} | Rest]) ->
+ [{iolist_to_binary(App), iolist_to_binary(Vsn),
+ normalize_unlocks_expect(Locks)} | normalize_unlocks(Rest)].
+
+normalize_unlocks_expect({error, Reason}) ->
+ {error, Reason};
+normalize_unlocks_expect([]) ->
+ [];
+normalize_unlocks_expect([{App,Vsn} | Rest]) ->
+ [{iolist_to_binary(App), iolist_to_binary(Vsn)}
+ | normalize_unlocks_expect(Rest)];
+normalize_unlocks_expect([App | Rest]) ->
+ [iolist_to_binary(App) | normalize_unlocks_expect(Rest)].
+
+top(Config) -> run(Config).
+pair(Config) -> run(Config).
+triplet(Config) -> run(Config).
+tree(Config) -> run(Config).
+
+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, []}),
+ Unlocks = ?config(unlocks, Config),
+ [begin
+ ct:pal("Unlocks: ~p -> ~p", [App, Unlocked]),
+ Expectation = case Unlocked of
+ Tuple when is_tuple(Tuple) -> Tuple;
+ _ -> {unlocked, Unlocked}
+ end,
+ rebar_test_utils:run_and_check(
+ Config, RebarConfig, ["upgrade", App], Expectation
+ )
+ end || {App, Unlocked} <- Unlocks].
+