您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

254 行
11 KiB

  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_prv_update).
  4. -behaviour(provider).
  5. -export([init/1,
  6. do/1,
  7. format_error/1]).
  8. -export([hex_to_index/1]).
  9. -ifdef(TEST).
  10. -export([cmp_/6, cmpl_/6, valid_vsn/1]).
  11. -endif.
  12. -include("rebar.hrl").
  13. -include_lib("providers/include/providers.hrl").
  14. -define(PROVIDER, update).
  15. -define(DEPS, []).
  16. %% ===================================================================
  17. %% Public API
  18. %% ===================================================================
  19. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  20. init(State) ->
  21. State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
  22. {module, ?MODULE},
  23. {bare, true},
  24. {deps, ?DEPS},
  25. {example, "rebar3 update"},
  26. {short_desc, "Update package index."},
  27. {desc, "Update package index."},
  28. {opts, []}])),
  29. {ok, State1}.
  30. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  31. do(State) ->
  32. try
  33. case rebar_packages:registry_dir(State) of
  34. {ok, RegistryDir} ->
  35. filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
  36. HexFile = filename:join(RegistryDir, "registry"),
  37. ?INFO("Updating package registry...", []),
  38. TmpDir = ec_file:insecure_mkdtemp(),
  39. TmpFile = filename:join(TmpDir, "packages.gz"),
  40. CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
  41. case rebar_utils:url_append_path(CDN, ?REMOTE_REGISTRY_FILE) of
  42. {ok, Url} ->
  43. ?DEBUG("Fetching registry from ~p", [Url]),
  44. case httpc:request(get, {Url, [{"User-Agent", rebar_utils:user_agent()}]},
  45. [], [{stream, TmpFile}, {sync, true}],
  46. rebar) of
  47. {ok, saved_to_file} ->
  48. {ok, Data} = file:read_file(TmpFile),
  49. Unzipped = zlib:gunzip(Data),
  50. ok = file:write_file(HexFile, Unzipped),
  51. ?INFO("Writing registry to ~s", [HexFile]),
  52. hex_to_index(State),
  53. {ok, State};
  54. _ ->
  55. ?PRV_ERROR(package_index_download)
  56. end;
  57. _ ->
  58. ?PRV_ERROR({package_parse_cdn, CDN})
  59. end;
  60. {uri_parse_error, CDN} ->
  61. ?PRV_ERROR({package_parse_cdn, CDN})
  62. end
  63. catch
  64. _E:C ->
  65. ?DEBUG("Error creating package index: ~p ~p", [C, erlang:get_stacktrace()]),
  66. throw(?PRV_ERROR(package_index_write))
  67. end.
  68. -spec format_error(any()) -> iolist().
  69. format_error({package_parse_cdn, Uri}) ->
  70. io_lib:format("Failed to parse CDN url: ~p", [Uri]);
  71. format_error(package_index_download) ->
  72. "Failed to download package index.";
  73. format_error(package_index_write) ->
  74. "Failed to write package index.".
  75. is_supported(<<"make">>) -> true;
  76. is_supported(<<"rebar">>) -> true;
  77. is_supported(<<"rebar3">>) -> true;
  78. is_supported(_) -> false.
  79. hex_to_index(State) ->
  80. {ok, RegistryDir} = rebar_packages:registry_dir(State),
  81. HexFile = filename:join(RegistryDir, "registry"),
  82. try ets:file2tab(HexFile) of
  83. {ok, Registry} ->
  84. try
  85. PackageIndex = filename:join(RegistryDir, "packages.idx"),
  86. ?INFO("Generating package index...", []),
  87. (catch ets:delete(?PACKAGE_TABLE)),
  88. ets:new(?PACKAGE_TABLE, [named_table, public]),
  89. ets:foldl(fun({{Pkg, PkgVsn}, [Deps, Checksum, BuildTools | _]}, _) when is_list(BuildTools) ->
  90. case lists:any(fun is_supported/1, BuildTools) of
  91. true ->
  92. DepsList = update_deps_list(Pkg, PkgVsn, Deps, Registry, State),
  93. ets:insert(?PACKAGE_TABLE, {{Pkg, PkgVsn}, DepsList, Checksum});
  94. false ->
  95. true
  96. end;
  97. (_, _) ->
  98. true
  99. end, true, Registry),
  100. ets:foldl(fun({Pkg, [[]]}, _) when is_binary(Pkg) ->
  101. true;
  102. ({Pkg, [Vsns=[_Vsn | _Rest]]}, _) when is_binary(Pkg) ->
  103. %% Verify the package is of the right build tool by checking if the first
  104. %% version exists in the table from the foldl above
  105. case [V || V <- Vsns, ets:member(?PACKAGE_TABLE, {Pkg, V})] of
  106. [] ->
  107. true;
  108. Vsns1 ->
  109. ets:insert(?PACKAGE_TABLE, {Pkg, Vsns1})
  110. end;
  111. (_, _) ->
  112. true
  113. end, true, Registry),
  114. ets:insert(?PACKAGE_TABLE, {package_index_version, ?PACKAGE_INDEX_VERSION}),
  115. ?INFO("Writing index to ~s", [PackageIndex]),
  116. ets:tab2file(?PACKAGE_TABLE, PackageIndex),
  117. true
  118. after
  119. catch ets:delete(Registry)
  120. end;
  121. {error, Reason} ->
  122. ?DEBUG("Error loading package registry: ~p", [Reason]),
  123. false
  124. catch
  125. _:_ ->
  126. fail
  127. end.
  128. update_deps_list(Pkg, PkgVsn, Deps, HexRegistry, State) ->
  129. lists:foldl(fun([Dep, DepVsn, false, _AppName | _], DepsListAcc) ->
  130. Dep1 = {Pkg, PkgVsn, Dep},
  131. case {valid_vsn(DepVsn), DepVsn} of
  132. %% Those are all not perfectly implemented!
  133. %% and doubled since spaces seem not to be
  134. %% enforced
  135. {false, Vsn} ->
  136. ?WARN("[~s:~s], Bad dependency version for ~s: ~s.",
  137. [Pkg, PkgVsn, Dep, Vsn]),
  138. DepsListAcc;
  139. {_, <<"~>", Vsn/binary>>} ->
  140. highest_matching(Dep1, rm_ws(Vsn), HexRegistry,
  141. State, DepsListAcc);
  142. {_, <<">=", Vsn/binary>>} ->
  143. cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
  144. DepsListAcc, fun ec_semver:gte/2);
  145. {_, <<">", Vsn/binary>>} ->
  146. cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
  147. DepsListAcc, fun ec_semver:gt/2);
  148. {_, <<"<=", Vsn/binary>>} ->
  149. cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
  150. DepsListAcc, fun ec_semver:lte/2);
  151. {_, <<"<", Vsn/binary>>} ->
  152. cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
  153. DepsListAcc, fun ec_semver:lt/2);
  154. {_, <<"==", Vsn/binary>>} ->
  155. [{Dep, Vsn} | DepsListAcc];
  156. {_, Vsn} ->
  157. [{Dep, Vsn} | DepsListAcc]
  158. end;
  159. ([_Dep, _DepVsn, true, _AppName | _], DepsListAcc) ->
  160. DepsListAcc
  161. end, [], Deps).
  162. rm_ws(<<" ", R/binary>>) ->
  163. rm_ws(R);
  164. rm_ws(R) ->
  165. R.
  166. valid_vsn(Vsn) ->
  167. %% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js
  168. SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?"
  169. "(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?",
  170. SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$",
  171. re:run(Vsn, SupportedVersions) =/= nomatch.
  172. highest_matching({Pkg, PkgVsn, Dep}, Vsn, HexRegistry, State, DepsListAcc) ->
  173. case rebar_packages:find_highest_matching(Pkg, PkgVsn, Dep, Vsn, HexRegistry, State) of
  174. {ok, HighestDepVsn} ->
  175. [{Dep, HighestDepVsn} | DepsListAcc];
  176. none ->
  177. ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
  178. [Pkg, PkgVsn, Dep]),
  179. DepsListAcc
  180. end.
  181. cmp({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
  182. {ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
  183. cmp_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
  184. cmp_(undefined, _MinVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) ->
  185. ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
  186. [Pkg, PkgVsn, Dep]),
  187. DepsListAcc;
  188. cmp_(HighestDepVsn, _MinVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) ->
  189. [{Dep, HighestDepVsn} | DepsListAcc];
  190. cmp_(BestMatch, MinVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
  191. case CmpFun(Vsn, MinVsn) of
  192. true ->
  193. cmp_(Vsn, Vsn, R, DepsListAcc, Dep, CmpFun);
  194. false ->
  195. cmp_(BestMatch, MinVsn, R, DepsListAcc, Dep, CmpFun)
  196. end.
  197. %% We need to treat this differently since we want a version that is LOWER but
  198. %% the higest possible one.
  199. cmpl({_Pkg, _PkgVsn, Dep} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
  200. {ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
  201. cmpl_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
  202. cmpl_(undefined, _MaxVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep}, _CmpFun) ->
  203. ?WARN("[~s:~s] Missing registry entry for package ~s. Try to fix with `rebar3 update`",
  204. [Pkg, PkgVsn, Dep]),
  205. DepsListAcc;
  206. cmpl_(HighestDepVsn, _MaxVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep}, _CmpFun) ->
  207. [{Dep, HighestDepVsn} | DepsListAcc];
  208. cmpl_(undefined, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
  209. case CmpFun(Vsn, MaxVsn) of
  210. true ->
  211. cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
  212. false ->
  213. cmpl_(undefined, MaxVsn, R, DepsListAcc, Dep, CmpFun)
  214. end;
  215. cmpl_(BestMatch, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
  216. case CmpFun(Vsn, MaxVsn) of
  217. true ->
  218. case ec_semver:gte(Vsn, BestMatch) of
  219. true ->
  220. cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
  221. false ->
  222. cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
  223. end;
  224. false ->
  225. cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
  226. end.