Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

191 řádky
8.4 KiB

  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.