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.

254 lines
11 KiB

преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
  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 ets:member(?PACKAGE_TABLE, {Pkg, Vsn}) of
  106. true ->
  107. ets:insert(?PACKAGE_TABLE, {Pkg, Vsns});
  108. false ->
  109. true
  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.