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.

391 lines
15 KiB

пре 15 година
пре 15 година
support for hex v2, multiple repository fetching, private organizations (#1884) * update to hex_core for hex-v2 repo support (#1865) * update to hex_core for hex-v2 repo support This patch adds only single repo hex-v2 support through hex_core. Packages no longer filtered out by buildtool metadata and the package index is updated per-package instead of fetched as one large ets dump. * tell travis to also build hex_core branch * support list of repos for hex packages (#1866) * support list of repos for hex packages repos are defined under the hex key in rebar configs. They can be defined at the top level of a project or globally, but not in profiles and the repos configured in dependencies are also ignored. Searching for packages involves first checking for a match in the local repo index cache, in the order repos are defined. If not found each repo is checked through the hex api for any known versions of the package and the first repo with a version that fits the constraint is used. * add {repos, replace, []} for overriding the global & default repos * add hex auth handling for repos (#1874) auth token are kept in a hex.config file that is modified by the rebar3 hex plugin. Repo names that have a : separating a parent and child are considered organizations. The parent repo's auth will be included with the child. So an organization named hexpm:rebar3_test will include any hexpm auth tokens found in the rebar3_test organization's configuration. * move packages to top level of of hexpm cache dir (#1876) * move packages to top level of of hexpm cache dir * append organization name to parent's repo_url when parsing repos * only eval config scripts and apply overrides once per app (#1879) * only eval config scripts and apply overrides once per app * move new resource behaviour to rebar_resource_v2 and keep v1 * cleanup use of rebar_resource module and unused functions * cleanup error messages and unused code * when discovering apps support mix packages as unbuilt apps (#1882) * use hex_core tarball unpacking support in pkg resource (#1883) * use hex_core tarball unpacking support in pkg resource * ignore etag if package doesn't exist and delete if checksum fails * add back tests for bad package checksums * improve bad registry checksum error message
пре 6 година
пре 10 година
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_config).
  28. -export([consult_root/0
  29. ,consult/1
  30. ,consult_app_file/1
  31. ,consult_file/1
  32. ,consult_lock_file/1
  33. ,write_lock_file/2
  34. ,verify_config_format/1
  35. ,format_error/1
  36. ,merge_locks/2]).
  37. -include("rebar.hrl").
  38. -include_lib("providers/include/providers.hrl").
  39. -define(DEFAULT_CONFIG_FILE, "rebar.config").
  40. %% ===================================================================
  41. %% Public API
  42. %% ===================================================================
  43. %% @doc reads the default config file at the top of a full project
  44. -spec consult_root() -> [any()].
  45. consult_root() ->
  46. consult_file(config_file()).
  47. %% @doc reads the default config file in a given directory.
  48. -spec consult(file:name()) -> [any()].
  49. consult(Dir) ->
  50. consult_file(filename:join(Dir, ?DEFAULT_CONFIG_FILE)).
  51. %% @doc reads a given app file, including the `.script' variations,
  52. %% if any can be found.
  53. -spec consult_app_file(file:filename()) -> [any()].
  54. consult_app_file(File) ->
  55. consult_file_(File).
  56. %% @doc reads the lock file for the project, and re-formats its
  57. %% content to match the internals for rebar3.
  58. -spec consult_lock_file(file:filename()) -> [any()]. % TODO: refine lock()
  59. consult_lock_file(File) ->
  60. Terms = consult_file_(File),
  61. case Terms of
  62. [] ->
  63. [];
  64. [Locks] when is_list(Locks) -> % beta lock file
  65. read_attrs(beta, Locks, []);
  66. [{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file
  67. %% Because this is the first version of rebar3 to introduce a lock
  68. %% file, all versioned lock files with a different version have
  69. %% to be newer.
  70. case Vsn of
  71. ?CONFIG_VERSION ->
  72. ok;
  73. _ ->
  74. %% Make sure the warning below is to be shown whenever a version
  75. %% newer than the current one is being used, as we can't parse
  76. %% all the contents of the lock file properly.
  77. warn_vsn_once()
  78. end,
  79. read_attrs(Vsn, Locks, Attrs)
  80. end.
  81. %% @private outputs a warning for a newer lockfile format than supported
  82. %% at most once.
  83. %% The warning can also be cancelled by configuring the `warn_config_vsn'
  84. %% OTP env variable.
  85. -spec warn_vsn_once() -> ok.
  86. warn_vsn_once() ->
  87. Warn = application:get_env(rebar, warn_config_vsn) =/= {ok, false},
  88. application:set_env(rebar, warn_config_vsn, false),
  89. case Warn of
  90. false -> ok;
  91. true ->
  92. ?WARN("Rebar3 detected a lock file from a newer version. "
  93. "It will be loaded in compatibility mode, but important "
  94. "information may be missing or lost. It is recommended to "
  95. "upgrade Rebar3.", [])
  96. end.
  97. %% @doc Converts the internal format for locks into the multi-version
  98. %% compatible one used within rebar3 lock files.
  99. %% @end
  100. %% TODO: refine type for lock()
  101. -spec write_lock_file(file:filename(), [any()]) -> ok | {error, term()}.
  102. write_lock_file(LockFile, Locks) ->
  103. {NewLocks, Attrs} = write_attrs(Locks),
  104. %% Write locks in the beta format, at least until it's been long
  105. %% enough we can start modifying the lock format.
  106. case Attrs of
  107. [] -> % write the old beta copy to avoid changes
  108. file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks]));
  109. _ ->
  110. file:write_file(LockFile,
  111. io_lib:format("{~p,~n~p}.~n[~n~ts~n].~n",
  112. [?CONFIG_VERSION, NewLocks,
  113. format_attrs(Attrs)]))
  114. end.
  115. %% @private Because attributes for packages are fairly large, there is the need
  116. %% for a special formatting to ensure there's only one entry per lock file
  117. %% line and that diffs are generally stable.
  118. -spec format_attrs([term()]) -> iodata().
  119. format_attrs([]) -> [];
  120. format_attrs([{pkg_hash, Vals}|T]) ->
  121. [io_lib:format("{pkg_hash,[~n",[]), format_hashes(Vals), "]}",
  122. maybe_comma(T) | format_attrs(T)].
  123. %% @private format hashing in order to disturb source diffing as little
  124. %% as possible
  125. -spec format_hashes([term()]) -> iodata().
  126. format_hashes([]) -> [];
  127. format_hashes([{Pkg,Hash}|T]) ->
  128. [" {", io_lib:format("~p",[Pkg]), ", ", io_lib:format("~p", [Hash]), "}",
  129. maybe_comma(T) | format_hashes(T)].
  130. %% @private add a comma if we're not done with the full list of terms
  131. %% to convert.
  132. -spec maybe_comma([term()]) -> iodata().
  133. maybe_comma([]) -> "";
  134. maybe_comma([_|_]) -> io_lib:format(",~n", []).
  135. %% @private extract attributes from the lock file and integrate them
  136. %% into the full-blow internal lock format
  137. %% @end
  138. %% TODO: refine typings for lock()
  139. -spec read_attrs(_, [any()], [any()]) -> [any()].
  140. read_attrs(_Vsn, Locks, Attrs) ->
  141. %% Beta copy does not know how to expand attributes, but
  142. %% is ready to support it.
  143. expand_locks(Locks, extract_pkg_hashes(Attrs)).
  144. %% @private extract the package hashes from lockfile attributes, if any.
  145. -spec extract_pkg_hashes(list()) -> [binary()].
  146. extract_pkg_hashes(Attrs) ->
  147. Props = case Attrs of
  148. [First|_] -> First;
  149. [] -> []
  150. end,
  151. proplists:get_value(pkg_hash, Props, []).
  152. %% @private extract attributes from the lock file and integrate them
  153. %% into the full-blow internal lock format
  154. %% @end
  155. %% TODO: refine typings for lock()
  156. -spec expand_locks(list(), list()) -> list().
  157. expand_locks([], _Hashes) ->
  158. [];
  159. expand_locks([{Name, {pkg,PkgName,Vsn}, Lvl} | Locks], Hashes) ->
  160. Hash = proplists:get_value(Name, Hashes),
  161. [{Name, {pkg,PkgName,Vsn,Hash}, Lvl} | expand_locks(Locks, Hashes)];
  162. expand_locks([Lock|Locks], Hashes) ->
  163. [Lock | expand_locks(Locks, Hashes)].
  164. %% @private split up extra attributes for locks out of the internal lock
  165. %% structure for backwards compatibility reasons
  166. -spec write_attrs(list()) -> {list(), list()}.
  167. write_attrs(Locks) ->
  168. %% No attribute known that needs to be taken out of the structure,
  169. %% just return terms as is.
  170. {NewLocks, Hashes} = split_locks(Locks, [], []),
  171. case Hashes of
  172. [] -> {NewLocks, []};
  173. _ -> {NewLocks, [{pkg_hash, lists:sort(Hashes)}]}
  174. end.
  175. %% @private split up extra attributes for locks out of the internal lock
  176. %% structure for backwards compatibility reasons
  177. -spec split_locks(list(), list(), [{_,binary()}]) -> {list(), list()}.
  178. split_locks([], Locks, Hashes) ->
  179. {lists:reverse(Locks), Hashes};
  180. split_locks([{Name, {pkg,PkgName,Vsn,undefined}, Lvl} | Locks], LAcc, HAcc) ->
  181. split_locks(Locks, [{Name,{pkg,PkgName,Vsn},Lvl}|LAcc], HAcc);
  182. split_locks([{Name, {pkg,PkgName,Vsn,Hash}, Lvl} | Locks], LAcc, HAcc) ->
  183. split_locks(Locks, [{Name,{pkg,PkgName,Vsn},Lvl}|LAcc], [{Name, Hash}|HAcc]);
  184. split_locks([Lock|Locks], LAcc, HAcc) ->
  185. split_locks(Locks, [Lock|LAcc], HAcc).
  186. %% @doc reads a given config file, including the `.script' variations,
  187. %% if any can be found, and asserts that the config format is in
  188. %% a key-value format.
  189. -spec consult_file(file:filename()) -> [{_,_}].
  190. consult_file(File) ->
  191. Terms = consult_file_(File),
  192. true = verify_config_format(Terms),
  193. Terms.
  194. %% @private reads a given file; if the file has a `.script'-postfixed
  195. %% counterpart, it is evaluated along with the original file.
  196. -spec consult_file_(file:name()) -> [any()].
  197. consult_file_(File) when is_binary(File) ->
  198. consult_file_(binary_to_list(File));
  199. consult_file_(File) ->
  200. case filename:extension(File) of
  201. ".script" ->
  202. {ok, Terms} = consult_and_eval(remove_script_ext(File), File),
  203. Terms;
  204. _ ->
  205. Script = File ++ ".script",
  206. case filelib:is_regular(Script) of
  207. true ->
  208. {ok, Terms} = consult_and_eval(File, Script),
  209. Terms;
  210. false ->
  211. rebar_file_utils:try_consult(File)
  212. end
  213. end.
  214. %% @private checks that a list is in a key-value format.
  215. %% Raises an exception in any other case.
  216. -spec verify_config_format([{_,_}]) -> true.
  217. verify_config_format([]) ->
  218. true;
  219. verify_config_format([{_Key, _Value} | T]) ->
  220. verify_config_format(T);
  221. verify_config_format([Term | _]) ->
  222. throw(?PRV_ERROR({bad_config_format, Term})).
  223. %% @doc takes an existing configuration and the content of a lockfile
  224. %% and merges the locks into the config.
  225. -spec merge_locks([{_,_}], list()) -> [{_,_}].
  226. merge_locks(Config, []) ->
  227. %% no lockfile
  228. Config;
  229. merge_locks(Config, Locks) ->
  230. %% lockfile with entries
  231. ConfigDeps = proplists:get_value(deps, Config, []),
  232. %% We want the top level deps only from the lock file.
  233. %% This ensures deterministic overrides for configs.
  234. %% Then check if any new deps have been added to the config
  235. %% since it was locked.
  236. Deps = [X || X <- Locks, element(3, X) =:= 0],
  237. NewDeps = find_newly_added(ConfigDeps, Locks),
  238. [{{locks, default}, Locks}, {{deps, default}, NewDeps++Deps} | Config].
  239. %% @doc convert a given exception's payload into an io description.
  240. -spec format_error(any()) -> iolist().
  241. format_error({bad_config_format, Term}) ->
  242. io_lib:format("Unable to parse config. Term is not in {Key, Value} format:~n~p", [Term]);
  243. format_error({bad_dep_name, Dep}) ->
  244. io_lib:format("Dependency name must be an atom, instead found: ~p", [Dep]).
  245. %% ===================================================================
  246. %% Internal functions
  247. %% ===================================================================
  248. %% @private consults a consult file, then executes its related script file
  249. %% with the data returned from the consult.
  250. -spec consult_and_eval(File::file:name_all(), Script::file:name_all()) ->
  251. {ok, Terms::[term()]} |
  252. {error, Reason::term()}.
  253. consult_and_eval(File, Script) ->
  254. ?DEBUG("Evaluating config script ~p", [Script]),
  255. StateData = rebar_file_utils:try_consult(File),
  256. %% file:consult/1 always returns the terms as a list, however file:script
  257. %% can (and will) return any kind of term(), to make consult_and_eval
  258. %% work the same way as eval we ensure that when no list is returned we
  259. %% convert it in a list.
  260. case file:script(Script, bs([{'CONFIG', StateData}, {'SCRIPT', Script}])) of
  261. {ok, Terms} when is_list(Terms) ->
  262. {ok, Terms};
  263. {ok, Term} ->
  264. {ok, [Term]};
  265. Error ->
  266. ?ERROR("Error evaluating configuration script at ~p:~n~p~n",
  267. [Script, Error]),
  268. Error
  269. end.
  270. %% @private drops the .script extension from a filename.
  271. -spec remove_script_ext(file:filename()) -> file:filename().
  272. remove_script_ext(F) ->
  273. filename:rootname(F, ".script").
  274. %% @private sets up bindings for evaluations from a KV list.
  275. -spec bs([{_,_}]) -> erl_eval:binding_struct().
  276. bs(Vars) ->
  277. lists:foldl(fun({K,V}, Bs) ->
  278. erl_eval:add_binding(K, V, Bs)
  279. end, erl_eval:new_bindings(), Vars).
  280. %% @private Find deps that have been added to the config after the lock was created
  281. -spec find_newly_added(list(), list()) -> list().
  282. find_newly_added(ConfigDeps, LockedDeps) ->
  283. [D || {true, D} <- [check_newly_added(Dep, LockedDeps) || Dep <- ConfigDeps]].
  284. %% @private checks if a given dependency is not within the lock file.
  285. %% TODO: refine types for dependencies
  286. -spec check_newly_added(term(), list()) -> false | {true, term()}.
  287. check_newly_added({_, _}=Dep, LockedDeps) ->
  288. check_newly_added_(Dep, LockedDeps);
  289. check_newly_added({_, _, {pkg, _}}=Dep, LockedDeps) ->
  290. check_newly_added_(Dep, LockedDeps);
  291. check_newly_added({Name, _, Source}, LockedDeps) ->
  292. check_newly_added_({Name, Source}, LockedDeps);
  293. check_newly_added(Dep, LockedDeps) ->
  294. check_newly_added_(Dep, LockedDeps).
  295. %% @private checks if a given dependency is not within the lock file.
  296. %% TODO: refine types for dependencies
  297. %% @end
  298. -spec check_newly_added_(term(), list()) -> false | {true, term()}.
  299. %% get [raw] deps out of the way
  300. check_newly_added_({Name, Source, Opts}, LockedDeps) when is_tuple(Source),
  301. is_list(Opts) ->
  302. case check_newly_added_(Name, LockedDeps) of
  303. {true, Name1} ->
  304. {true, {Name1, Source}};
  305. false ->
  306. false
  307. end;
  308. check_newly_added_({Name,_Vsn,Source,Opts}, LockedDeps) when is_tuple(Source),
  309. is_list(Opts) ->
  310. case check_newly_added_(Name, LockedDeps) of
  311. {true, Name1} ->
  312. {true, {Name1, Source}};
  313. false ->
  314. false
  315. end;
  316. %% and on to regular deps
  317. check_newly_added_({Name, Vsn, Source}, LockedDeps) ->
  318. case check_newly_added_(Name, LockedDeps) of
  319. {true, Name1} ->
  320. {true, {Name1, Vsn, Source}};
  321. false ->
  322. false
  323. end;
  324. check_newly_added_({Name, Source}, LockedDeps) ->
  325. case check_newly_added_(Name, LockedDeps) of
  326. {true, Name1} ->
  327. {true, {Name1, Source}};
  328. false ->
  329. false
  330. end;
  331. check_newly_added_(Dep, LockedDeps) when is_atom(Dep) ->
  332. Name = rebar_utils:to_binary(Dep),
  333. case lists:keyfind(Name, 1, LockedDeps) of
  334. false ->
  335. {true, Name};
  336. Match ->
  337. case element(3, Match) of
  338. 0 ->
  339. {true, Name};
  340. _ ->
  341. ?WARN("Newly added dep ~ts is locked at a lower level. "
  342. "If you really want to unlock it, use 'rebar3 upgrade ~ts'",
  343. [Name, Name]),
  344. false
  345. end
  346. end;
  347. check_newly_added_(Dep, _) ->
  348. throw(?PRV_ERROR({bad_dep_name, Dep})).
  349. %% @private returns the name/path of the default config file, or its
  350. %% override from the OS ENV var `REBAR_CONFIG'.
  351. -spec config_file() -> file:filename().
  352. config_file() ->
  353. case os:getenv("REBAR_CONFIG") of
  354. false ->
  355. ?DEFAULT_CONFIG_FILE;
  356. ConfigFile ->
  357. ConfigFile
  358. end.