diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index 34631ff0..2de30e42 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -70,7 +70,9 @@ do(State) -> is_atom(Dep) orelse is_atom(element(1, Dep))], Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>)), Locks), DepsDict = deps_dict(rebar_state:all_deps(State)), - case prepare_locks(Names, Deps, Locks, [], DepsDict) of + AltDeps = find_non_default_deps(Deps, State), + FilteredNames = cull_default_names_if_profiles(Names, Deps, State), + case prepare_locks(FilteredNames, Deps, Locks, [], DepsDict, AltDeps) of {error, Reason} -> {error, Reason}; {Locks0, _Unlocks0} -> @@ -115,20 +117,45 @@ parse_names(Bin, Locks) -> Other -> Other end. -prepare_locks([], _, Locks, Unlocks, _Dict) -> +%% Find alternative deps in non-default profiles since they may +%% need to be passed through (they are never locked) +find_non_default_deps(Deps, State) -> + AltProfiles = rebar_state:current_profiles(State) -- [default], + AltProfileDeps = lists:append([ + rebar_state:get(State, {deps, Profile}, []) || Profile <- AltProfiles] + ), + [Dep || Dep <- AltProfileDeps, + is_atom(Dep) orelse is_atom(element(1, Dep)) + andalso not lists:member(Dep, Deps)]. + +%% If any alt profiles are used, remove the default profiles from +%% the upgrade list and warn about it. +cull_default_names_if_profiles(Names, Deps, State) -> + case rebar_state:current_profiles(State) of + [default] -> + Names; + _ -> + ?INFO("Dependencies in the default profile will not be upgraded", []), + lists:filter(fun(Name) -> + AtomName = binary_to_atom(Name, utf8), + rebar_utils:tup_find(AtomName, Deps) == false + end, Names) + end. + +prepare_locks([], _, Locks, Unlocks, _Dict, _AltDeps) -> {Locks, Unlocks}; -prepare_locks([Name|Names], Deps, Locks, Unlocks, Dict) -> +prepare_locks([Name|Names], Deps, Locks, Unlocks, Dict, AltDeps) -> AtomName = binary_to_atom(Name, utf8), case lists:keyfind(Name, 1, Locks) of {_, _, 0} = Lock -> case rebar_utils:tup_find(AtomName, Deps) of false -> ?WARN("Dependency ~s has been removed and will not be upgraded", [Name]), - prepare_locks(Names, Deps, Locks, Unlocks, Dict); + prepare_locks(Names, Deps, Locks, Unlocks, Dict, AltDeps); Dep -> {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks, Dict), prepare_locks(Names, Deps, NewLocks, - [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict) + [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict, AltDeps) end; {_, _, Level} = Lock when Level > 0 -> case rebar_utils:tup_find(AtomName, Deps) of @@ -137,10 +164,15 @@ prepare_locks([Name|Names], Deps, Locks, Unlocks, Dict) -> Dep -> % Dep has been promoted {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks, Dict), prepare_locks(Names, Deps, NewLocks, - [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict) + [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict, AltDeps) end; false -> - ?PRV_ERROR({unknown_dependency, Name}) + case rebar_utils:tup_find(AtomName, AltDeps) of + false -> + ?PRV_ERROR({unknown_dependency, Name}); + _ -> % non-default profile dependency found, pass through + prepare_locks(Names, Deps, Locks, Unlocks, Dict, AltDeps) + end end. prepare_lock(Dep, Lock, Locks, Dict) -> diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl index 66e1fdfc..45a74336 100644 --- a/test/rebar_upgrade_SUITE.erl +++ b/test/rebar_upgrade_SUITE.erl @@ -11,7 +11,8 @@ groups() -> triplet_a, triplet_b, triplet_c, tree_a, tree_b, tree_c, tree_c2, tree_cj, tree_ac, tree_all, delete_d, promote, stable_lock, fwd_lock, - compile_upgrade_parity, umbrella_config]}, + compile_upgrade_parity, umbrella_config, + profiles, profiles_exclusion]}, {git, [], [{group, all}]}, {pkg, [], [{group, all}]}]. @@ -78,6 +79,23 @@ setup_project(Case=umbrella_config, Config0, Deps, UpDeps) -> [{rebarconfig, TopConf}, {rebarumbrella, RebarConf}, {next_top_deps, rebar_test_utils:top_level_deps(UpDeps)} | Config]; +setup_project(Case, Config0, Deps, UpDeps) when Case == profiles; + Case == profiles_exclusion -> + DepsType = ?config(deps_type, Config0), + NameRoot = atom_to_list(Case)++"_"++atom_to_list(DepsType), + Config = rebar_test_utils:init_rebar_state(Config0, NameRoot++"_"), + AppDir = filename:join([?config(apps, Config), "apps", NameRoot]), + rebar_test_utils:create_app(AppDir, "Root", "0.0.0", [kernel, stdlib]), + [Top|ProfileDeps] = rebar_test_utils:top_level_deps(Deps), + RebarConf = rebar_test_utils:create_config(AppDir, [ + {deps, [Top]}, + {profiles, [{fake, [{deps, ProfileDeps}]}]} + ]), + [NextTop|NextPDeps] = rebar_test_utils:top_level_deps(UpDeps), + NextConfig = [{deps, [NextTop]}, + {profiles, [{fake, [{deps, NextPDeps}]}]}], + [{rebarconfig, RebarConf}, + {next_config, NextConfig} | Config]; setup_project(Case, Config0, Deps, UpDeps) -> DepsType = ?config(deps_type, Config0), Config = rebar_test_utils:init_rebar_state( @@ -454,7 +472,47 @@ upgrades(umbrella_config) -> {[{"A", "1", []}], [{"A", "2", []}], ["A"], - {"A", [{"A","2"}]}}. + {"A", [{"A","2"}]}}; +upgrades(profiles) -> + %% Ensure that we can unlock deps under a given profile; + %% B and C should both be in a custom profile + %% and must not be locked. + {[{"A", "1", [{"D",[]}, + {"E","3",[]}]}, + {"B", "1", [{"F","1",[]}, + {"G",[]}]}, + {"C", "0", [{"H","3",[]}, + {"I",[]}]}], + [{"A", "2", [{"D",[]}, + {"E","2",[]}]}, + {"B", "2", [{"F","2",[]}, + {"G",[]}]}, + {"C", "1", [{"H","4",[]}, + {"I",[]}]}], + ["A","B","C","E","F","H"], + {"C", [{"A","1"}, "D", {"E","3"}, + {"B","2"}, {"F","2"}, "G", + {"C","1"}, {"H","4"}, "I"]}}; +upgrades(profiles_exclusion) -> + %% Ensure that we can unlock deps under a given profile; + %% B and C should both be in a custom profile + %% and must not be locked. + {[{"A", "1", [{"D",[]}, + {"E","3",[]}]}, + {"B", "1", [{"F","1",[]}, + {"G",[]}]}, + {"C", "0", [{"H","3",[]}, + {"I",[]}]}], + [{"A", "2", [{"D",[]}, + {"E","2",[]}]}, + {"B", "2", [{"F","2",[]}, + {"G",[]}]}, + {"C", "1", [{"H","4",[]}, + {"I",[]}]}], + ["A","B","C","E","F","H"], + {"A", [{"A","1"}, "D", {"E","3"}, + {"B","2"}, {"F","2"}, "G", + {"C","1"}, {"H","4"}, "I"]}}. %% TODO: add a test that verifies that unlocking files and then %% running the upgrade code is enough to properly upgrade things. @@ -613,6 +671,37 @@ umbrella_config(Config) -> ), meck:unload(rebar_prv_upgrade). +profiles(Config) -> + apply(?config(mock, Config), []), + {ok, TopConfig} = file:consult(?config(rebarconfig, Config)), + %% Install dependencies before re-mocking for an upgrade + rebar_test_utils:run_and_check(Config, TopConfig, ["lock"], {ok, []}), + %% Install test deps along with them + rebar_test_utils:run_and_check(Config, TopConfig, ["as","fake","lock"], {ok, []}), + {App, Unlocks} = ?config(expected, Config), + ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]), + Expectation = case Unlocks of + {error, Term} -> {error, Term}; + _ -> {ok, [T || T <- Unlocks, + element(1,T) == dep orelse + lists:member(element(2,T), ["A","D","E"])]} + end, + + meck:new(rebar_prv_app_discovery, [passthrough]), + meck:expect(rebar_prv_app_discovery, do, fun(S) -> + apply(?config(mock_update, Config), []), + meck:passthrough([S]) + end), + NewRebarConf = rebar_test_utils:create_config(?config(apps, Config), + ?config(next_config, Config)), + {ok, NewRebarConfig} = file:consult(NewRebarConf), + rebar_test_utils:run_and_check( + Config, NewRebarConfig, ["as","fake","upgrade", App], Expectation + ), + meck:unload(rebar_prv_app_discovery). + +profiles_exclusion(Config) -> profiles(Config). + run(Config) -> apply(?config(mock, Config), []), ConfigPath = ?config(rebarconfig, Config),