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.

366 lines
14 KiB

  1. -module(rebar_packages).
  2. -export([get/2
  3. ,get_all_names/1
  4. ,get_package_versions/3
  5. ,get_package_deps/3
  6. ,new_package_table/0
  7. ,load_and_verify_version/1
  8. ,registry_dir/1
  9. ,package_dir/1
  10. ,registry_checksum/3
  11. ,find_highest_matching/6
  12. ,find_highest_matching/4
  13. ,find_highest_matching_/6
  14. ,verify_table/1
  15. ,format_error/1
  16. ,update_package/2
  17. ,resolve_version/4]).
  18. -ifdef(TEST).
  19. -export([cmp_/4, cmpl_/4, valid_vsn/1]).
  20. -endif.
  21. -export_type([package/0]).
  22. -include("rebar.hrl").
  23. -include_lib("providers/include/providers.hrl").
  24. -type pkg_name() :: binary() | atom().
  25. -type vsn() :: binary().
  26. -type package() :: pkg_name() | {pkg_name(), vsn()}.
  27. format_error({missing_package, Name, Vsn}) ->
  28. io_lib:format("Package not found in registry: ~ts-~ts.", [rebar_utils:to_binary(Name),
  29. rebar_utils:to_binary(Vsn)]);
  30. format_error({missing_package, Pkg}) ->
  31. io_lib:format("Package not found in registry: ~p.", [Pkg]).
  32. -spec get(hex_core:config(), binary()) -> {ok, map()} | {error, term()}.
  33. get(Config, Name) ->
  34. case hex_api_package:get(Config, Name) of
  35. {ok, {200, _Headers, PkgInfo}} ->
  36. {ok, PkgInfo};
  37. _ ->
  38. {error, blewup}
  39. end.
  40. -spec get_all_names(rebar_state:t()) -> [binary()].
  41. get_all_names(State) ->
  42. verify_table(State),
  43. lists:usort(ets:select(?PACKAGE_TABLE, [{#package{key={'$1', '_'},
  44. _='_'},
  45. [], ['$1']}])).
  46. -spec get_package_versions(binary(), ets:tid(), rebar_state:t()) -> [vsn()].
  47. get_package_versions(Dep, Table, State) ->
  48. ?MODULE:verify_table(State),
  49. ets:select(Table, [{#package{key={Dep,'$1'},
  50. _='_'},
  51. [], ['$1']}]).
  52. new_package_table() ->
  53. ets:new(?PACKAGE_TABLE, [named_table, public, ordered_set, {keypos, 2}]),
  54. ets:insert(package_index, {?PACKAGE_INDEX_VERSION, package_index_version}).
  55. -spec get_package_deps(binary(), vsn(), rebar_state:t()) -> [map()].
  56. get_package_deps(Name, Vsn, State) ->
  57. try_lookup(?PACKAGE_TABLE, {Name, Vsn}, #package.dependencies, State).
  58. -spec registry_checksum(binary(), vsn(), rebar_state:t()) -> binary().
  59. registry_checksum(Name, Vsn, State) ->
  60. try_lookup(?PACKAGE_TABLE, {Name, Vsn}, #package.checksum, State).
  61. try_lookup(Table, Key, Element, State) ->
  62. ?MODULE:verify_table(State),
  63. try
  64. ets:lookup_element(Table, Key, Element)
  65. catch
  66. _:_ ->
  67. handle_missing_package(Key, State, fun(_) ->
  68. ets:lookup_element(Table, Key, Element)
  69. end)
  70. end.
  71. load_and_verify_version(State) ->
  72. {ok, RegistryDir} = registry_dir(State),
  73. case ets:file2tab(filename:join(RegistryDir, ?INDEX_FILE)) of
  74. {ok, _} ->
  75. case ets:lookup_element(?PACKAGE_TABLE, package_index_version, 1) of
  76. ?PACKAGE_INDEX_VERSION ->
  77. true;
  78. V ->
  79. %% no reason to confuse the user since we just start fresh and they
  80. %% shouldn't notice, so log as a debug message only
  81. ?DEBUG("Package index version mismatch. Current version ~p, this rebar3 expecting ~p",
  82. [V, ?PACKAGE_INDEX_VERSION]),
  83. (catch ets:delete(?PACKAGE_TABLE)),
  84. new_package_table()
  85. end;
  86. _ ->
  87. new_package_table()
  88. end.
  89. handle_missing_package(PkgKey, State, Fun) ->
  90. Name =
  91. case PkgKey of
  92. {N, Vsn} ->
  93. ?DEBUG("Package ~ts-~ts not found. Fetching registry updates for "
  94. "package and trying again...", [N, Vsn]),
  95. N;
  96. _ ->
  97. ?DEBUG("Package ~p not found. Fetching registry updates for "
  98. "package and trying again...", [PkgKey]),
  99. PkgKey
  100. end,
  101. update_package(Name, State),
  102. try
  103. Fun(State)
  104. catch
  105. _:_ ->
  106. %% Even after an update the package is still missing, time to error out
  107. throw(?PRV_ERROR({missing_package, PkgKey}))
  108. end.
  109. registry_dir(State) ->
  110. CacheDir = rebar_dir:global_cache_dir(rebar_state:opts(State)),
  111. case rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN) of
  112. ?DEFAULT_CDN ->
  113. RegistryDir = filename:join([CacheDir, "hex", "default"]),
  114. case filelib:ensure_dir(filename:join(RegistryDir, "placeholder")) of
  115. ok -> ok;
  116. {error, Posix} when Posix == eaccess; Posix == enoent ->
  117. ?ABORT("Could not write to ~p. Please ensure the path is writeable.",
  118. [RegistryDir])
  119. end,
  120. {ok, RegistryDir};
  121. CDN ->
  122. case rebar_utils:url_append_path(CDN, ?REMOTE_PACKAGE_DIR) of
  123. {ok, Parsed} ->
  124. {ok, {_, _, Host, _, Path, _}} = http_uri:parse(Parsed),
  125. CDNHostPath = lists:reverse(rebar_string:lexemes(Host, ".")),
  126. CDNPath = tl(filename:split(Path)),
  127. RegistryDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath),
  128. ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
  129. {ok, RegistryDir};
  130. _ ->
  131. {uri_parse_error, CDN}
  132. end
  133. end.
  134. package_dir(State) ->
  135. case registry_dir(State) of
  136. {ok, RegistryDir} ->
  137. PackageDir = filename:join([RegistryDir, "packages"]),
  138. ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
  139. {ok, PackageDir};
  140. Error ->
  141. Error
  142. end.
  143. %% Hex supports use of ~> to specify the version required for a dependency.
  144. %% Since rebar3 requires exact versions to choose from we find the highest
  145. %% available version of the dep that passes the constraint.
  146. %% `~>` will never include pre-release versions of its upper bound.
  147. %% It can also be used to set an upper bound on only the major
  148. %% version part. See the table below for `~>` requirements and
  149. %% their corresponding translation.
  150. %% `~>` | Translation
  151. %% :------------- | :---------------------
  152. %% `~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
  153. %% `~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
  154. %% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
  155. %% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
  156. %% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
  157. find_highest_matching(Dep, Constraint, Table, State) ->
  158. find_highest_matching(undefined, undefined, Dep, Constraint, Table, State).
  159. find_highest_matching(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
  160. try find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) of
  161. none ->
  162. handle_missing_package(Dep, State,
  163. fun(State1) ->
  164. find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
  165. end);
  166. Result ->
  167. Result
  168. catch
  169. _:_ ->
  170. handle_missing_package(Dep, State,
  171. fun(State1) ->
  172. find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
  173. end)
  174. end.
  175. find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
  176. try get_package_versions(Dep, Table, State) of
  177. [Vsn] ->
  178. handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint);
  179. Vsns ->
  180. case handle_vsns(Constraint, Vsns) of
  181. none ->
  182. none;
  183. FoundVsn ->
  184. {ok, FoundVsn}
  185. end
  186. catch
  187. error:badarg ->
  188. none
  189. end.
  190. handle_vsns(Constraint, Vsns) ->
  191. lists:foldl(fun(Version, Highest) ->
  192. case ec_semver:pes(Version, Constraint) andalso
  193. (Highest =:= none orelse ec_semver:gt(Version, Highest)) of
  194. true ->
  195. Version;
  196. false ->
  197. Highest
  198. end
  199. end, none, Vsns).
  200. handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint) ->
  201. case ec_semver:pes(Vsn, Constraint) of
  202. true ->
  203. {ok, Vsn};
  204. false ->
  205. case {Pkg, PkgVsn} of
  206. {undefined, undefined} ->
  207. ?DEBUG("Only existing version of ~ts is ~ts which does not match constraint ~~> ~ts. "
  208. "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]);
  209. _ ->
  210. ?DEBUG("[~ts:~ts] Only existing version of ~ts is ~ts which does not match constraint ~~> ~ts. "
  211. "Using anyway, but it is not guaranteed to work.", [Pkg, PkgVsn, Dep, Vsn, Constraint])
  212. end,
  213. {ok, Vsn}
  214. end.
  215. verify_table(State) ->
  216. ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State).
  217. parse_deps(Deps) ->
  218. [{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}}
  219. || D=#{package := Name,
  220. requirement := Constraint} <- Deps].
  221. parse_checksum(<<Checksum:256/big-unsigned>>) ->
  222. list_to_binary(
  223. rebar_string:uppercase(
  224. lists:flatten(io_lib:format("~64.16.0b", [Checksum]))));
  225. parse_checksum(Checksum) ->
  226. Checksum.
  227. update_package(Name, State) ->
  228. Resources = rebar_state:resources(State),
  229. #{hex_config := HexConfig} = rebar_resource:find_resource_state(pkg, Resources),
  230. case hex_repo:get_package(HexConfig, Name) of
  231. {ok, {200, _Headers, #{releases := Releases}}} ->
  232. _ = insert_releases(Name, Releases, ?PACKAGE_TABLE),
  233. {ok, RegistryDir} = rebar_packages:registry_dir(State),
  234. PackageIndex = filename:join(RegistryDir, ?INDEX_FILE),
  235. ok = ets:tab2file(?PACKAGE_TABLE, PackageIndex);
  236. _ ->
  237. fail
  238. end.
  239. insert_releases(Name, Releases, Table) ->
  240. [true = ets:insert(Table,
  241. #package{key={Name, Version},
  242. checksum=parse_checksum(Checksum),
  243. dependencies=parse_deps(Dependencies)})
  244. || #{checksum := Checksum,
  245. version := Version,
  246. dependencies := Dependencies} <- Releases].
  247. resolve_version(Dep, undefined, HexRegistry, State) ->
  248. find_highest_matching(Dep, "0", HexRegistry, State);
  249. resolve_version(Dep, DepVsn, HexRegistry, State) ->
  250. case {valid_vsn(DepVsn), DepVsn} of
  251. {false, Vsn} ->
  252. {error, {invalid_vsn, Vsn}};
  253. {_, <<"~>", Vsn/binary>>} ->
  254. highest_matching(Dep, rm_ws(Vsn), HexRegistry, State);
  255. {_, <<">=", Vsn/binary>>} ->
  256. cmp(Dep, rm_ws(Vsn), HexRegistry, State, fun ec_semver:gte/2);
  257. {_, <<">", Vsn/binary>>} ->
  258. cmp(Dep, rm_ws(Vsn), HexRegistry, State, fun ec_semver:gt/2);
  259. {_, <<"<=", Vsn/binary>>} ->
  260. cmpl(Dep, rm_ws(Vsn), HexRegistry, State, fun ec_semver:lte/2);
  261. {_, <<"<", Vsn/binary>>} ->
  262. cmpl(Dep, rm_ws(Vsn), HexRegistry, State, fun ec_semver:lt/2);
  263. {_, <<"==", Vsn/binary>>} ->
  264. {ok, Vsn};
  265. {_, Vsn} ->
  266. {ok, Vsn}
  267. end.
  268. rm_ws(<<" ", R/binary>>) ->
  269. rm_ws(R);
  270. rm_ws(R) ->
  271. R.
  272. valid_vsn(Vsn) ->
  273. %% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js
  274. SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?"
  275. "(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?",
  276. SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$",
  277. re:run(Vsn, SupportedVersions, [unicode]) =/= nomatch.
  278. highest_matching(Dep, Vsn, HexRegistry, State) ->
  279. case find_highest_matching_(undefined, undefined, Dep, Vsn, HexRegistry, State) of
  280. {ok, HighestDepVsn} ->
  281. {ok, HighestDepVsn};
  282. none ->
  283. {error, {invalid_vsn, Vsn}}
  284. end.
  285. cmp(Dep, Vsn, HexRegistry, State, CmpFun) ->
  286. Vsns = get_package_versions(Dep, HexRegistry, State),
  287. cmp_(undefined, Vsn, Vsns, CmpFun).
  288. cmp_(undefined, MinVsn, [], _CmpFun) ->
  289. MinVsn;
  290. cmp_(HighestDepVsn, _MinVsn, [], _CmpFun) ->
  291. HighestDepVsn;
  292. cmp_(BestMatch, MinVsn, [Vsn | R], CmpFun) ->
  293. case CmpFun(Vsn, MinVsn) of
  294. true ->
  295. cmp_(Vsn, Vsn, R, CmpFun);
  296. false ->
  297. cmp_(BestMatch, MinVsn, R, CmpFun)
  298. end.
  299. %% We need to treat this differently since we want a version that is LOWER but
  300. %% the higest possible one.
  301. cmpl(Dep, Vsn, HexRegistry, State, CmpFun) ->
  302. Vsns = get_package_versions(Dep, HexRegistry, State),
  303. cmpl_(undefined, Vsn, Vsns, CmpFun).
  304. cmpl_(undefined, MaxVsn, [], _CmpFun) ->
  305. MaxVsn;
  306. cmpl_(HighestDepVsn, _MaxVsn, [], _CmpFun) ->
  307. HighestDepVsn;
  308. cmpl_(undefined, MaxVsn, [Vsn | R], CmpFun) ->
  309. case CmpFun(Vsn, MaxVsn) of
  310. true ->
  311. cmpl_(Vsn, MaxVsn, R, CmpFun);
  312. false ->
  313. cmpl_(undefined, MaxVsn, R, CmpFun)
  314. end;
  315. cmpl_(BestMatch, MaxVsn, [Vsn | R], CmpFun) ->
  316. case CmpFun(Vsn, MaxVsn) of
  317. true ->
  318. case ec_semver:gte(Vsn, BestMatch) of
  319. true ->
  320. cmpl_(Vsn, MaxVsn, R, CmpFun);
  321. false ->
  322. cmpl_(BestMatch, MaxVsn, R, CmpFun)
  323. end;
  324. false ->
  325. cmpl_(BestMatch, MaxVsn, R, CmpFun)
  326. end.