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.

182 regels
6.3 KiB

  1. -module(rebar_packages).
  2. -export([packages/1
  3. ,close_packages/0
  4. ,load_and_verify_version/1
  5. ,deps/3
  6. ,registry_dir/1
  7. ,package_dir/1
  8. ,registry_checksum/2
  9. ,find_highest_matching/4
  10. ,verify_table/1
  11. ,format_error/1]).
  12. -export_type([package/0]).
  13. -include("rebar.hrl").
  14. -include_lib("providers/include/providers.hrl").
  15. -type pkg_name() :: binary() | atom().
  16. -type vsn() :: binary().
  17. -type package() :: pkg_name() | {pkg_name(), vsn()}.
  18. -spec packages(rebar_state:t()) -> ets:tid().
  19. packages(State) ->
  20. catch ets:delete(?PACKAGE_TABLE),
  21. case load_and_verify_version(State) of
  22. true ->
  23. ok;
  24. false ->
  25. ?DEBUG("Error loading package index.", []),
  26. handle_bad_index(State)
  27. end.
  28. handle_bad_index(State) ->
  29. ?ERROR("Bad packages index. Trying to fix by updating the registry.", []),
  30. {ok, State1} = rebar_prv_update:do(State),
  31. case load_and_verify_version(State1) of
  32. true ->
  33. ok;
  34. false ->
  35. %% Still unable to load after an update, create an empty registry
  36. ets:new(?PACKAGE_TABLE, [named_table, public])
  37. end.
  38. close_packages() ->
  39. catch ets:delete(?PACKAGE_TABLE).
  40. load_and_verify_version(State) ->
  41. {ok, RegistryDir} = registry_dir(State),
  42. case ets:file2tab(filename:join(RegistryDir, ?INDEX_FILE)) of
  43. {ok, _} ->
  44. case ets:lookup_element(?PACKAGE_TABLE, package_index_version, 2) of
  45. ?PACKAGE_INDEX_VERSION ->
  46. true;
  47. _ ->
  48. (catch ets:delete(?PACKAGE_TABLE)),
  49. rebar_prv_update:hex_to_index(State)
  50. end;
  51. _ ->
  52. rebar_prv_update:hex_to_index(State)
  53. end.
  54. deps(Name, Vsn, State) ->
  55. try
  56. deps_(Name, Vsn, State)
  57. catch
  58. _:_ ->
  59. handle_missing_package(Name, Vsn, State)
  60. end.
  61. deps_(Name, Vsn, State) ->
  62. ?MODULE:verify_table(State),
  63. ets:lookup_element(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, 2).
  64. handle_missing_package(Name, Vsn, State) ->
  65. ?INFO("Package ~s-~s not found. Fetching registry updates and trying again...", [Name, Vsn]),
  66. {ok, State1} = rebar_prv_update:do(State),
  67. try
  68. deps_(Name, Vsn, State1)
  69. catch
  70. _:_ ->
  71. %% Even after an update the package is still missing, time to error out
  72. throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}))
  73. end.
  74. registry_dir(State) ->
  75. CacheDir = rebar_dir:global_cache_dir(rebar_state:opts(State)),
  76. case rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN) of
  77. ?DEFAULT_CDN ->
  78. RegistryDir = filename:join([CacheDir, "hex", "default"]),
  79. ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
  80. {ok, RegistryDir};
  81. CDN ->
  82. case rebar_utils:url_append_path(CDN, ?REMOTE_PACKAGE_DIR) of
  83. {ok, Parsed} ->
  84. {ok, {_, _, Host, _, Path, _}} = http_uri:parse(Parsed),
  85. CDNHostPath = lists:reverse(string:tokens(Host, ".")),
  86. CDNPath = tl(filename:split(Path)),
  87. RegistryDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath),
  88. ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
  89. {ok, RegistryDir};
  90. _ ->
  91. {uri_parse_error, CDN}
  92. end
  93. end.
  94. package_dir(State) ->
  95. case registry_dir(State) of
  96. {ok, RegistryDir} ->
  97. PackageDir = filename:join([RegistryDir, "packages"]),
  98. ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
  99. {ok, PackageDir};
  100. Error ->
  101. Error
  102. end.
  103. registry_checksum({pkg, Name, Vsn}, State) ->
  104. try
  105. ?MODULE:verify_table(State),
  106. ets:lookup_element(?PACKAGE_TABLE, {Name, Vsn}, 3)
  107. catch
  108. _:_ ->
  109. throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}))
  110. end.
  111. %% Hex supports use of ~> to specify the version required for a dependency.
  112. %% Since rebar3 requires exact versions to choose from we find the highest
  113. %% available version of the dep that passes the constraint.
  114. %% `~>` will never include pre-release versions of its upper bound.
  115. %% It can also be used to set an upper bound on only the major
  116. %% version part. See the table below for `~>` requirements and
  117. %% their corresponding translation.
  118. %% `~>` | Translation
  119. %% :------------- | :---------------------
  120. %% `~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
  121. %% `~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
  122. %% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
  123. %% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
  124. %% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
  125. find_highest_matching(Dep, Constraint, Table, State) ->
  126. ?MODULE:verify_table(State),
  127. try ets:lookup_element(Table, Dep, 2) of
  128. [[HeadVsn | VsnTail]] ->
  129. {ok, handle_vsns(Constraint, HeadVsn, VsnTail)};
  130. [[Vsn]] ->
  131. handle_single_vsn(Dep, Vsn, Constraint);
  132. [Vsn] ->
  133. handle_single_vsn(Dep, Vsn, Constraint);
  134. [HeadVsn | VsnTail] ->
  135. {ok, handle_vsns(Constraint, HeadVsn, VsnTail)}
  136. catch
  137. error:badarg ->
  138. none
  139. end.
  140. handle_vsns(Constraint, HeadVsn, VsnTail) ->
  141. lists:foldl(fun(Version, Highest) ->
  142. case ec_semver:pes(Version, Constraint) andalso
  143. ec_semver:gt(Version, Highest) of
  144. true ->
  145. Version;
  146. false ->
  147. Highest
  148. end
  149. end, HeadVsn, VsnTail).
  150. handle_single_vsn(Dep, Vsn, Constraint) ->
  151. case ec_semver:pes(Vsn, Constraint) of
  152. true ->
  153. {ok, Vsn};
  154. false ->
  155. ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
  156. "Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]),
  157. {ok, Vsn}
  158. end.
  159. format_error({missing_package, Package, Version}) ->
  160. io_lib:format("Package not found in registry: ~s-~s.", [Package, Version]).
  161. verify_table(State) ->
  162. ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State).