diff options
Diffstat (limited to 'src/rebar_prv_upgrade.erl')
-rw-r--r-- | src/rebar_prv_upgrade.erl | 109 |
1 files changed, 92 insertions, 17 deletions
diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index c0a56c3..1cae5aa 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -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. + |