You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 regels
8.4 KiB

10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_prv_upgrade).
  4. -behaviour(provider).
  5. -export([init/1,
  6. do/1,
  7. format_error/1]).
  8. -include("rebar.hrl").
  9. -include_lib("providers/include/providers.hrl").
  10. -define(PROVIDER, upgrade).
  11. -define(DEPS, [lock]).
  12. %% Also only upgrade top-level (0) deps. Transitive deps shouldn't be
  13. %% upgradable -- if the user wants this, they should declare it at the
  14. %% top level and then upgrade.
  15. %% ===================================================================
  16. %% Public API
  17. %% ===================================================================
  18. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  19. init(State) ->
  20. State1 =
  21. rebar_state:add_provider(State,
  22. providers:create([{name, ?PROVIDER},
  23. {module, ?MODULE},
  24. {bare, true},
  25. {deps, ?DEPS},
  26. {example, "rebar3 upgrade [cowboy[,ranch]]"},
  27. {short_desc, "Upgrade dependencies."},
  28. {desc, "Upgrade project dependecies. Mentioning no application "
  29. "will upgrade all dependencies. To upgrade specific dependencies, "
  30. "their names can be listed in the command."},
  31. {opts, [
  32. {package, undefined, undefined, string,
  33. "List of packages to upgrade. If not specified, all dependencies are upgraded."}
  34. ]}])),
  35. {ok, State1}.
  36. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  37. do(State) ->
  38. {Args, _} = rebar_state:command_parsed_args(State),
  39. Locks = rebar_state:get(State, {locks, default}, []),
  40. %% We have 3 sources of dependencies to upgrade from:
  41. %% 1. the top-level rebar.config (in `deps', dep name is an atom)
  42. %% 2. the app-level rebar.config in umbrella apps (in `{deps, default}',
  43. %% where the dep name is an atom)
  44. %% 3. the formatted sources for all after app-parsing (in `{deps, default}',
  45. %% where the reprocessed app name is a binary)
  46. %%
  47. %% The gotcha with these is that the selection of dependencies with a
  48. %% binary name (those that are stable and usable internally) is done with
  49. %% in the profile deps only, but while accounting for locks.
  50. %% Because our job here is to unlock those that have changed, we must
  51. %% instead use the atom-based names, both in `deps' and `{deps, default}',
  52. %% as those are the dependencies that may have changed but have been
  53. %% disregarded by locks.
  54. %%
  55. %% As such, the working set of dependencies is the addition of
  56. %% `deps' and `{deps, default}' entries with an atom name, as those
  57. %% disregard locks and parsed values post-selection altogether.
  58. %% Packages without versions can of course be a single atom.
  59. TopDeps = rebar_state:get(State, deps, []),
  60. ProfileDeps = rebar_state:get(State, {deps, default}, []),
  61. Deps = [Dep || Dep <- TopDeps ++ ProfileDeps, % TopDeps > ProfileDeps
  62. is_atom(Dep) orelse is_atom(element(1, Dep))],
  63. Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>)), Locks),
  64. DepsDict = deps_dict(rebar_state:all_deps(State)),
  65. case prepare_locks(Names, Deps, Locks, [], DepsDict) of
  66. {error, Reason} ->
  67. {error, Reason};
  68. {Locks0, _Unlocks0} ->
  69. Deps0 = top_level_deps(Deps, Locks),
  70. State1 = rebar_state:set(State, {deps, default}, Deps0),
  71. DepsDir = rebar_prv_install_deps:profile_dep_dir(State, default),
  72. D = rebar_app_utils:parse_deps(root, DepsDir, Deps0, State1, Locks0, 0),
  73. State2 = rebar_state:set(State1, {parsed_deps, default}, D),
  74. State3 = rebar_state:set(State2, {locks, default}, Locks0),
  75. State4 = rebar_state:set(State3, upgrade, true),
  76. UpdatedLocks = [L || L <- rebar_state:lock(State4),
  77. lists:keymember(rebar_app_info:name(L), 1, Locks0)],
  78. Res = rebar_prv_install_deps:do_(rebar_state:lock(State4, UpdatedLocks)),
  79. case Res of
  80. {ok, State5} ->
  81. rebar_utils:info_useless(
  82. [element(1,Lock) || Lock <- Locks],
  83. [rebar_app_info:name(App) || App <- rebar_state:lock(State5)]
  84. ),
  85. rebar_prv_lock:do(State5);
  86. _ ->
  87. Res
  88. end
  89. end.
  90. -spec format_error(any()) -> iolist().
  91. format_error({unknown_dependency, Name}) ->
  92. io_lib:format("Dependency ~ts not found", [Name]);
  93. format_error({transitive_dependency, Name}) ->
  94. io_lib:format("Dependency ~ts is transient and cannot be safely upgraded. "
  95. "Promote it to your top-level rebar.config file to upgrade it.",
  96. [Name]);
  97. format_error(Reason) ->
  98. io_lib:format("~p", [Reason]).
  99. parse_names(Bin, Locks) ->
  100. case lists:usort(re:split(Bin, <<" *, *">>, [trim])) of
  101. %% Nothing submitted, use *all* apps
  102. [<<"">>] -> [Name || {Name, _, 0} <- Locks];
  103. [] -> [Name || {Name, _, 0} <- Locks];
  104. %% Regular options
  105. Other -> Other
  106. end.
  107. prepare_locks([], _, Locks, Unlocks, _Dict) ->
  108. {Locks, Unlocks};
  109. prepare_locks([Name|Names], Deps, Locks, Unlocks, Dict) ->
  110. AtomName = binary_to_atom(Name, utf8),
  111. case lists:keyfind(Name, 1, Locks) of
  112. {_, _, 0} = Lock ->
  113. case rebar_utils:tup_find(AtomName, Deps) of
  114. false ->
  115. ?WARN("Dependency ~s has been removed and will not be upgraded", [Name]),
  116. prepare_locks(Names, Deps, Locks, Unlocks, Dict);
  117. Dep ->
  118. {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks, Dict),
  119. prepare_locks(Names, Deps, NewLocks,
  120. [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict)
  121. end;
  122. {_, _, Level} = Lock when Level > 0 ->
  123. case rebar_utils:tup_find(AtomName, Deps) of
  124. false ->
  125. ?PRV_ERROR({transitive_dependency, Name});
  126. Dep -> % Dep has been promoted
  127. {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks, Dict),
  128. prepare_locks(Names, Deps, NewLocks,
  129. [{Name, Source, 0} | NewUnlocks ++ Unlocks], Dict)
  130. end;
  131. false ->
  132. ?PRV_ERROR({unknown_dependency, Name})
  133. end.
  134. prepare_lock(Dep, Lock, Locks, Dict) ->
  135. {Name1, Source} = case Dep of
  136. {Name, SrcOrVsn} -> {Name, SrcOrVsn};
  137. {Name, _, Src} -> {Name, Src};
  138. _ when is_atom(Dep) ->
  139. %% version-free package. Must unlock whatever matches in locks
  140. {_, Vsn, _} = lists:keyfind(ec_cnv:to_binary(Dep), 1, Locks),
  141. {Dep, Vsn}
  142. end,
  143. Children = all_children(Name1, Dict),
  144. {NewLocks, NewUnlocks} = unlock_children(Children, Locks -- [Lock]),
  145. {Source, NewLocks, NewUnlocks}.
  146. top_level_deps(Deps, Locks) ->
  147. [Dep || Dep <- Deps, lists:keymember(0, 3, Locks)].
  148. unlock_children(Children, Locks) ->
  149. unlock_children(Children, Locks, [], []).
  150. unlock_children(_, [], Locks, Unlocks) ->
  151. {Locks, Unlocks};
  152. unlock_children(Children, [App = {Name,_,_} | Apps], Locks, Unlocks) ->
  153. case lists:member(ec_cnv:to_binary(Name), Children) of
  154. true ->
  155. unlock_children(Children, Apps, Locks, [App | Unlocks]);
  156. false ->
  157. unlock_children(Children, Apps, [App | Locks], Unlocks)
  158. end.
  159. deps_dict(Deps) ->
  160. lists:foldl(fun(App, Dict) ->
  161. Name = rebar_app_info:name(App),
  162. Parent = rebar_app_info:parent(App),
  163. dict:append_list(Parent, [Name], Dict)
  164. end, dict:new(), Deps).
  165. all_children(Name, Dict) ->
  166. lists:flatten(all_children_(Name, Dict)).
  167. all_children_(Name, Dict) ->
  168. case dict:find(ec_cnv:to_binary(Name), Dict) of
  169. {ok, Children} ->
  170. [Children | [all_children_(Child, Dict) || Child <- Children]];
  171. error ->
  172. []
  173. end.