summaryrefslogtreecommitdiff
path: root/src/rebar_prv_upgrade.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rebar_prv_upgrade.erl')
-rw-r--r--src/rebar_prv_upgrade.erl109
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.
+