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.

1168 lines
42 KiB

пре 15 година
пре 15 година
пре 10 година
пре 10 година
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 година
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 година
пре 15 година
пре 10 година
пре 12 година
пре 14 година
пре 10 година
пре 14 година
пре 14 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 9 година
пре 9 година
пре 9 година
пре 9 година
пре 9 година
пре 7 година
пре 10 година
пре 13 година
пре 13 година
пре 12 година
пре 13 година
пре 13 година
пре 14 година
пре 13 година
пре 9 година
пре 14 година
пре 10 година
пре 10 година
пре 14 година
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 година
пре 9 година
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 година
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 година
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 година
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 година
пре 13 година
пре 13 година
пре 9 година
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 година
  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, 2010 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_utils).
  28. -export([sort_deps/1,
  29. droplast/1,
  30. filtermap/2,
  31. is_arch/1,
  32. sh/2,
  33. sh_send/3,
  34. abort/0,
  35. abort/2,
  36. escript_foldl/3,
  37. find_files/2,
  38. find_files/3,
  39. find_files_in_dirs/3,
  40. find_source/3,
  41. beam_to_mod/1,
  42. erl_to_mod/1,
  43. beams/1,
  44. find_executable/1,
  45. vcs_vsn/3,
  46. deprecated/3,
  47. deprecated/4,
  48. indent/1,
  49. update_code/1,
  50. update_code/2,
  51. remove_from_code_path/1,
  52. cleanup_code_path/1,
  53. args_to_tasks/1,
  54. expand_env_variable/3,
  55. get_arch/0,
  56. wordsize/0,
  57. deps_to_binary/1,
  58. to_binary/1,
  59. to_list/1,
  60. tup_dedup/1,
  61. tup_umerge/2,
  62. tup_sort/1,
  63. tup_find/2,
  64. line_count/1,
  65. set_httpc_options/0,
  66. url_append_path/2,
  67. escape_chars/1,
  68. escape_double_quotes/1,
  69. escape_double_quotes_weak/1,
  70. check_min_otp_version/1,
  71. check_blacklisted_otp_versions/1,
  72. info_useless/2,
  73. list_dir/1,
  74. user_agent/0,
  75. reread_config/1, reread_config/2,
  76. get_proxy_auth/0,
  77. is_list_of_strings/1,
  78. ssl_opts/1]).
  79. %% for internal use only
  80. -export([otp_release/0]).
  81. -include("rebar.hrl").
  82. -include_lib("public_key/include/OTP-PUB-KEY.hrl").
  83. -define(ONE_LEVEL_INDENT, " ").
  84. -define(APP_NAME_INDEX, 2).
  85. %% ====================================================================
  86. %% Public API
  87. %% ====================================================================
  88. sort_deps(Deps) ->
  89. %% We need a sort stable, based on the name. So that for multiple deps on
  90. %% the same level with the same name, we keep the order the parents had.
  91. %% `lists:keysort/2' is documented as stable in the stdlib.
  92. %% The list of deps is revered when we get it. For the proper stable
  93. %% result, re-reverse it.
  94. lists:keysort(?APP_NAME_INDEX, lists:reverse(Deps)).
  95. droplast(L) ->
  96. lists:reverse(tl(lists:reverse(L))).
  97. %% @doc filtermap takes in a function that is either or both
  98. %% a predicate and a map, and returns the matching and valid elements.
  99. -spec filtermap(F, [In]) -> [Out] when
  100. F :: fun((In) -> boolean() | {true, Out}),
  101. In :: term(),
  102. Out :: term().
  103. filtermap(F, [Hd|Tail]) ->
  104. case F(Hd) of
  105. true ->
  106. [Hd|filtermap(F, Tail)];
  107. {true,Val} ->
  108. [Val|filtermap(F, Tail)];
  109. false ->
  110. filtermap(F, Tail)
  111. end;
  112. filtermap(F, []) when is_function(F, 1) -> [].
  113. is_arch(ArchRegex) ->
  114. case re:run(get_arch(), ArchRegex, [{capture, none}, unicode]) of
  115. match ->
  116. true;
  117. nomatch ->
  118. false
  119. end.
  120. %% @doc returns the sytem architecture, in strings like
  121. %% `"19.0.4-x86_64-unknown-linux-gnu-64"'.
  122. -spec get_arch() -> string().
  123. get_arch() ->
  124. Words = wordsize(),
  125. otp_release() ++ "-"
  126. ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
  127. %% @doc returns the size of a word on the system, as a string
  128. -spec wordsize() -> string().
  129. wordsize() ->
  130. try erlang:system_info({wordsize, external}) of
  131. Val ->
  132. integer_to_list(8 * Val)
  133. catch
  134. error:badarg ->
  135. integer_to_list(8 * erlang:system_info(wordsize))
  136. end.
  137. sh_send(Command0, String, Options0) ->
  138. ?INFO("sh_send info:\n\tcwd: ~p\n\tcmd: ~ts < ~ts\n",
  139. [rebar_dir:get_cwd(), Command0, String]),
  140. ?DEBUG("\topts: ~p\n", [Options0]),
  141. DefaultOptions = [use_stdout, abort_on_error],
  142. Options = [expand_sh_flag(V)
  143. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  144. Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options0, []))),
  145. PortSettings = proplists:get_all_values(port_settings, Options) ++
  146. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
  147. Port = open_port({spawn, Command}, PortSettings),
  148. try
  149. %% allow us to send some data to the shell command's STDIN
  150. %% Erlang doesn't let us get any reply after sending an EOF, though...
  151. Port ! {self(), {command, String}}
  152. after
  153. port_close(Port)
  154. end.
  155. %%
  156. %% Options = [Option] -- defaults to [use_stdout, abort_on_error]
  157. %% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env}
  158. %% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()}
  159. %% OutputOption = use_stdout | {use_stdout, bool()}
  160. %% Env = [{string(), Val}]
  161. %% Val = string() | false
  162. %%
  163. sh(Command0, Options0) ->
  164. ?DEBUG("sh info:\n\tcwd: ~p\n\tcmd: ~ts\n", [rebar_dir:get_cwd(), Command0]),
  165. ?DEBUG("\topts: ~p\n", [Options0]),
  166. DefaultOptions = [{use_stdout, false}, debug_and_abort_on_error],
  167. Options = [expand_sh_flag(V)
  168. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  169. ErrorHandler = proplists:get_value(error_handler, Options),
  170. OutputHandler = proplists:get_value(output_handler, Options),
  171. Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options0, []))),
  172. PortSettings = proplists:get_all_values(port_settings, Options) ++
  173. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof],
  174. ?DEBUG("Port Cmd: ~ts\nPort Opts: ~p\n", [Command, PortSettings]),
  175. Port = open_port({spawn, Command}, PortSettings),
  176. try
  177. case sh_loop(Port, OutputHandler, []) of
  178. {ok, _Output} = Ok ->
  179. Ok;
  180. {error, {_Rc, _Output}=Err} ->
  181. ErrorHandler(Command, Err)
  182. end
  183. after
  184. port_close(Port)
  185. end.
  186. find_files(Dir, Regex) ->
  187. find_files(Dir, Regex, true).
  188. find_files_in_dirs([], _Regex, _Recursive) ->
  189. [];
  190. find_files_in_dirs([Dir | T], Regex, Recursive) ->
  191. find_files(Dir, Regex, Recursive) ++ find_files_in_dirs(T, Regex, Recursive).
  192. find_files(Dir, Regex, Recursive) ->
  193. filelib:fold_files(Dir, Regex, Recursive,
  194. fun(F, Acc) -> [F | Acc] end, []).
  195. find_executable(Name) ->
  196. case os:find_executable(Name) of
  197. false -> false;
  198. Path ->
  199. "\"" ++ filename:nativename(Path) ++ "\""
  200. end.
  201. deprecated(Old, New, Opts, When) when is_list(Opts) ->
  202. case lists:member(Old, Opts) of
  203. true ->
  204. deprecated(Old, New, When);
  205. false ->
  206. ok
  207. end;
  208. deprecated(Old, New, Config, When) ->
  209. case rebar_state:get(Config, Old, undefined) of
  210. undefined ->
  211. ok;
  212. _ ->
  213. deprecated(Old, New, When)
  214. end.
  215. deprecated(Old, New, When) ->
  216. io:format(
  217. <<"WARNING: deprecated ~p option used~n"
  218. "Option '~p' has been deprecated~n"
  219. "in favor of '~p'.~n"
  220. "'~p' will be removed ~ts.~n">>,
  221. [Old, Old, New, Old, When]).
  222. %% for use by `do` task
  223. %% note: this does not handle the case where you have an argument that
  224. %% was enclosed in quotes and might have commas but should not be split.
  225. args_to_tasks(Args) -> new_task(Args, []).
  226. deps_to_binary([]) ->
  227. [];
  228. deps_to_binary([{Name, _, Source} | T]) ->
  229. [{to_binary(Name), Source} | deps_to_binary(T)];
  230. deps_to_binary([{Name, Source} | T]) ->
  231. [{to_binary(Name), Source} | deps_to_binary(T)];
  232. deps_to_binary([Name | T]) ->
  233. [to_binary(Name) | deps_to_binary(T)].
  234. to_binary(A) when is_atom(A) -> atom_to_binary(A, unicode);
  235. to_binary(Str) -> unicode:characters_to_binary(Str).
  236. to_list(A) when is_atom(A) -> atom_to_list(A);
  237. to_list(Str) -> unicode:characters_to_list(Str).
  238. tup_dedup(List) ->
  239. tup_dedup_(tup_sort(List)).
  240. tup_dedup_([]) ->
  241. [];
  242. tup_dedup_([A]) ->
  243. [A];
  244. tup_dedup_([A,B|T]) when element(1, A) =:= element(1, B) ->
  245. tup_dedup_([A | T]);
  246. tup_dedup_([A,B|T]) when element(1, A) =:= B ->
  247. tup_dedup_([A | T]);
  248. tup_dedup_([A,B|T]) when A =:= element(1, B) ->
  249. tup_dedup_([A | T]);
  250. tup_dedup_([A,A|T]) ->
  251. [A|tup_dedup_(T)];
  252. tup_dedup_([A|T]) ->
  253. [A|tup_dedup_(T)].
  254. %% Sort the list in proplist-order, meaning that `{a,b}' and `{a,c}'
  255. %% both compare as usual, and `a' and `b' do the same, but `a' and `{a,b}' will
  256. %% compare based on the first element of the key, and in order. So the following
  257. %% list will sort as:
  258. %% - `[native, {native,o3}, check]' -> `[check, native, {native, o3}]'
  259. %% - `[native, {native,o3}, {native, o2}, check]' -> `[check,native,{native,o3},{native,o2}]'
  260. %% Meaning that:
  261. %% a) no deduplication takes place
  262. %% b) the key of a tuple is what counts in being sorted, but atoms are seen as {atom}
  263. %% as far as comparison is concerned (departing from lists:ukeysort/2)
  264. %% c) order is preserved for similar keys and tuples no matter their size (sort is stable)
  265. %%
  266. %% These properties let us merge proplists fairly easily.
  267. tup_sort(List) ->
  268. lists:sort(fun(A, B) when is_tuple(A), is_tuple(B) -> element(1, A) =< element(1, B)
  269. ; (A, B) when is_tuple(A) -> element(1, A) =< B
  270. ; (A, B) when is_tuple(B) -> A =< element(1, B)
  271. ; (A, B) -> A =< B
  272. end, List).
  273. %% Custom merge functions. The objective is to behave like lists:umerge/2,
  274. %% except that we also compare the merge elements based on the key if they're a
  275. %% tuple, such that `{key, val1}' is always prioritized over `{key, val0}' if
  276. %% the former is from the 'new' list.
  277. %%
  278. %% This lets us apply proper overrides to list of elements according to profile
  279. %% priority. This function depends on a stable proplist sort.
  280. tup_umerge(NewList, OldList) ->
  281. tup_umerge_(tup_sort(NewList), tup_sort(OldList)).
  282. tup_umerge_([], Olds) ->
  283. Olds;
  284. tup_umerge_([New|News], Olds) ->
  285. tup_umerge_dedup_(umerge(new, News, Olds, [], New), []).
  286. %% removes 100% identical duplicate elements so that
  287. %% `[a,{a,b},a,{a,c},a]' returns `[a,{a,b},{a,c}]'.
  288. %% Operates on a reverted list that gets reversed as part of this pass
  289. tup_umerge_dedup_([], Acc) ->
  290. Acc;
  291. tup_umerge_dedup_([H|T], Acc) ->
  292. case lists:member(H,T) of
  293. true -> tup_umerge_dedup_(T, Acc);
  294. false -> tup_umerge_dedup_(T, [H|Acc])
  295. end.
  296. tup_find(_Elem, []) ->
  297. false;
  298. tup_find(Elem, [Elem | _Elems]) ->
  299. Elem;
  300. tup_find(Elem, [Elem1 | Elems]) when is_tuple(Elem1) ->
  301. case element(1, Elem1) =:= Elem of
  302. true ->
  303. Elem1;
  304. false ->
  305. tup_find(Elem, Elems)
  306. end;
  307. tup_find(Elem, [_Elem | Elems]) ->
  308. tup_find(Elem, Elems).
  309. -spec umerge(new|old, News, Olds, Acc, Current) -> Merged when
  310. News :: [term()],
  311. Olds :: [term()],
  312. Acc :: [term()],
  313. Current :: term(),
  314. Merged :: [term()].
  315. umerge(_, [], [], Acc, Current) ->
  316. [Current | Acc];
  317. umerge(new, News, [], Acc, Current) ->
  318. %% only news left
  319. lists:reverse(News, [Current|Acc]);
  320. umerge(old, [], Olds, Acc, Current) ->
  321. %% only olds left
  322. lists:reverse(Olds, [Current|Acc]);
  323. umerge(new, News, [Old|Olds], Acc, Current) ->
  324. {Dir, Merged, NewCurrent} = compare({new, Current}, {old, Old}),
  325. umerge(Dir, News, Olds, [Merged|Acc], NewCurrent);
  326. umerge(old, [New|News], Olds, Acc, Current) ->
  327. {Dir, Merged, NewCurrent} = compare({new, New}, {old, Current}),
  328. umerge(Dir, News, Olds, [Merged|Acc], NewCurrent).
  329. -spec compare({Priority, term()}, {Secondary, term()}) ->
  330. {NextPriority, Merged, Larger} when
  331. Priority :: new | old,
  332. Secondary :: new | old,
  333. NextPriority :: new | old,
  334. Merged :: term(),
  335. Larger :: term().
  336. compare({Priority, A}, {Secondary, B}) when is_tuple(A), is_tuple(B) ->
  337. KA = element(1,A),
  338. KB = element(1,B),
  339. if KA == KB -> {Secondary, A, B};
  340. KA < KB -> {Secondary, A, B};
  341. KA > KB -> {Priority, B, A}
  342. end;
  343. compare({Priority, A}, {Secondary, B}) when not is_tuple(A), not is_tuple(B) ->
  344. if A == B -> {Secondary, A, B};
  345. A < B -> {Secondary, A, B};
  346. A > B -> {Priority, B, A}
  347. end;
  348. compare({Priority, A}, {Secondary, B}) when is_tuple(A), not is_tuple(B) ->
  349. KA = element(1,A),
  350. if KA == B -> {Secondary, A, B};
  351. KA < B -> {Secondary, A, B};
  352. KA > B -> {Priority, B, A}
  353. end;
  354. compare({Priority, A}, {Secondary, B}) when not is_tuple(A), is_tuple(B) ->
  355. KB = element(1,B),
  356. if A == KB -> {Secondary, A, B};
  357. A < KB -> {Secondary, A, B};
  358. A > KB -> {Priority, B, A}
  359. end.
  360. %% Implements wc -l functionality used to determine patchcount from git output
  361. line_count(PatchLines) ->
  362. Tokenized = rebar_string:lexemes(PatchLines, "\n"),
  363. {ok, length(Tokenized)}.
  364. check_min_otp_version(undefined) ->
  365. ok;
  366. check_min_otp_version(MinOtpVersion) ->
  367. %% Fully-qualify with ?MODULE so the function can be meck'd in rebar_utils_SUITE
  368. OtpRelease = ?MODULE:otp_release(),
  369. ParsedMin = version_tuple(MinOtpVersion),
  370. ParsedVsn = version_tuple(OtpRelease),
  371. case ParsedVsn >= ParsedMin of
  372. true ->
  373. ?DEBUG("~ts satisfies the requirement for minimum OTP version ~ts",
  374. [OtpRelease, MinOtpVersion]);
  375. false ->
  376. ?ABORT("OTP release ~ts or later is required. Version in use: ~ts",
  377. [MinOtpVersion, OtpRelease])
  378. end.
  379. check_blacklisted_otp_versions(undefined) ->
  380. ok;
  381. check_blacklisted_otp_versions(BlacklistedRegexes) ->
  382. %% Fully-qualify with ?MODULE so the function can be meck'd in rebar_utils_SUITE
  383. OtpRelease = ?MODULE:otp_release(),
  384. lists:foreach(
  385. fun(BlacklistedRegex) -> abort_if_blacklisted(BlacklistedRegex, OtpRelease) end,
  386. BlacklistedRegexes).
  387. abort_if_blacklisted(BlacklistedRegex, OtpRelease) ->
  388. case re:run(OtpRelease, BlacklistedRegex, [{capture, none}]) of
  389. match ->
  390. ?ABORT("OTP release ~ts matches blacklisted version ~ts",
  391. [OtpRelease, BlacklistedRegex]);
  392. nomatch ->
  393. ?DEBUG("~ts does not match blacklisted OTP version ~ts",
  394. [OtpRelease, BlacklistedRegex])
  395. end.
  396. user_agent() ->
  397. {ok, Vsn} = application:get_key(rebar, vsn),
  398. ?FMT("Rebar/~ts (OTP/~ts)", [Vsn, otp_release()]).
  399. reread_config(ConfigList) ->
  400. %% Default to not re-configuring the logger for now;
  401. %% this can leak logs in CT redirection when setting up hooks
  402. %% for example. If we want to turn it on by default, we may
  403. %% want to disable it in CT at the same time or figure out a
  404. %% way to silence it.
  405. %% The same pattern may apply to other tasks, so let's enable
  406. %% case-by-case.
  407. reread_config(ConfigList, []).
  408. reread_config(ConfigList, Opts) ->
  409. UpdateLoggerConfig = erlang:function_exported(logger, module_info, 0) andalso
  410. proplists:get_value(update_logger, Opts, false),
  411. %% NB: we attempt to mimic -config here, which survives app reload,
  412. %% hence {persistent, true}.
  413. SetEnv = case version_tuple(?MODULE:otp_release()) of
  414. {X, _, _} when X < 17 ->
  415. fun application:set_env/3;
  416. _ ->
  417. fun (App, Key, Val) -> application:set_env(App, Key, Val, [{persistent, true}]) end
  418. end,
  419. try
  420. Res =
  421. [SetEnv(Application, Key, Val)
  422. || Config <- ConfigList,
  423. {Application, Items} <- Config,
  424. {Key, Val} <- Items],
  425. case UpdateLoggerConfig of
  426. true -> reread_logger_config();
  427. false -> ok
  428. end,
  429. Res
  430. catch _:_ ->
  431. ?ERROR("The configuration file submitted could not be read "
  432. "and will be ignored.", [])
  433. end.
  434. %% @private since the kernel app is already booted, re-reading its config
  435. %% requires doing some magic to dynamically patch running handlers to
  436. %% deal with the current value.
  437. reread_logger_config() ->
  438. KernelCfg = application:get_all_env(kernel),
  439. LogCfg = proplists:get_value(logger, KernelCfg),
  440. case LogCfg of
  441. undefined ->
  442. ok;
  443. _ ->
  444. %% Extract and apply settings related to primary configuration
  445. %% -- primary config is used for settings shared across handlers
  446. LogLvlPrimary = proplists:get_value(logger_level, KernelCfg, all),
  447. {FilterDefault, Filters} =
  448. case lists:keyfind(filters, 1, KernelCfg) of
  449. false -> {log, []};
  450. {filters, FoundDef, FoundFilter} -> {FoundDef, FoundFilter}
  451. end,
  452. Primary = #{level => LogLvlPrimary,
  453. filter_default => FilterDefault,
  454. filters => Filters},
  455. %% Load the correct handlers based on their individual config.
  456. [case Id of
  457. default -> logger:update_handler_config(Id, Cfg);
  458. _ -> logger:add_handler(Id, Mod, Cfg)
  459. end || {handler, Id, Mod, Cfg} <- LogCfg],
  460. logger:set_primary_config(Primary),
  461. ok
  462. end.
  463. %% @doc Given env. variable `FOO' we want to expand all references to
  464. %% it in `InStr'. References can have two forms: `$FOO' and `${FOO}'
  465. %% The end of form `$FOO' is delimited with whitespace or EOL
  466. -spec expand_env_variable(string(), string(), term()) -> string().
  467. expand_env_variable(InStr, VarName, RawVarValue) ->
  468. case rebar_string:chr(InStr, $$) of
  469. 0 ->
  470. %% No variables to expand
  471. InStr;
  472. _ ->
  473. ReOpts = [global, unicode, {return, list}],
  474. VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
  475. %% Use a regex to match/replace:
  476. %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
  477. RegEx = io_lib:format("\\\$(~ts(\\W|$)|{~ts})", [VarName, VarName]),
  478. re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
  479. end.
  480. %% ====================================================================
  481. %% Internal functions
  482. %% ====================================================================
  483. version_tuple(OtpRelease) ->
  484. case re:run(OtpRelease, "R?(\\d+)B?.?-?(\\d+)?.?-?(\\d+)?", [{capture, all, list}]) of
  485. {match, [_Full, Maj, Min, Patch]} ->
  486. {list_to_integer(Maj), list_to_integer(Min), list_to_integer(Patch)};
  487. {match, [_Full, Maj, Min]} ->
  488. {list_to_integer(Maj), list_to_integer(Min), 0};
  489. {match, [_Full, Maj]} ->
  490. {list_to_integer(Maj), 0, 0};
  491. nomatch ->
  492. ?ABORT("Minimum OTP release unable to be parsed: ~ts", [OtpRelease])
  493. end.
  494. otp_release() ->
  495. otp_release1(erlang:system_info(otp_release)).
  496. %% If OTP <= R16, otp_release is already what we want.
  497. otp_release1([$R,N|_]=Rel) when is_integer(N) ->
  498. Rel;
  499. %% If OTP >= 17.x, erlang:system_info(otp_release) returns just the
  500. %% major version number, we have to read the full version from
  501. %% a file. See http://www.erlang.org/doc/system_principles/versions.html
  502. %% Read vsn string from the 'OTP_VERSION' file and return as list without
  503. %% the "\n".
  504. otp_release1(Rel) ->
  505. File = filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]),
  506. case file:read_file(File) of
  507. {error, _} ->
  508. Rel;
  509. {ok, Vsn} ->
  510. %% It's fine to rely on the binary module here because we can
  511. %% be sure that it's available when the otp_release string does
  512. %% not begin with $R.
  513. %% The shortest vsn string consists of at least two digits
  514. %% followed by "\n". Therefore, it's safe to assume Size >= 3.
  515. case binary:match(Vsn, <<"**">>) of
  516. {Pos, _} ->
  517. %% The OTP documentation mentions that a system patched
  518. %% using the otp_patch_apply tool available to licensed
  519. %% customers will leave a '**' suffix in the version as a
  520. %% flag saying the system consists of application versions
  521. %% from multiple OTP versions. We ignore this flag and
  522. %% drop the suffix, given for all intents and purposes, we
  523. %% cannot obtain relevant information from it as far as
  524. %% tooling is concerned.
  525. binary:bin_to_list(Vsn, {0, Pos});
  526. nomatch ->
  527. rebar_string:trim(binary:bin_to_list(Vsn), trailing, "\n")
  528. end
  529. end.
  530. %% We do the shell variable substitution ourselves on Windows and hope that the
  531. %% command doesn't use any other shell magic.
  532. patch_on_windows(Cmd, Env) ->
  533. case os:type() of
  534. {win32,nt} ->
  535. Cmd1 = "cmd /q /c "
  536. ++ lists:foldl(fun({Key, Value}, Acc) ->
  537. expand_env_variable(Acc, Key, Value)
  538. end, Cmd, Env),
  539. %% Remove left-over vars
  540. re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
  541. [global, {return, list}, unicode]);
  542. _ ->
  543. Cmd
  544. end.
  545. expand_sh_flag(return_on_error) ->
  546. {error_handler,
  547. fun(_Command, Err) ->
  548. {error, Err}
  549. end};
  550. expand_sh_flag(abort_on_error) ->
  551. {error_handler,
  552. fun log_and_abort/2};
  553. expand_sh_flag({abort_on_error, Message}) ->
  554. {error_handler,
  555. log_msg_and_abort(Message)};
  556. expand_sh_flag({debug_abort_on_error, Message}) ->
  557. {error_handler,
  558. debug_log_msg_and_abort(Message)};
  559. expand_sh_flag(debug_and_abort_on_error) ->
  560. {error_handler,
  561. fun debug_and_abort/2};
  562. expand_sh_flag(use_stdout) ->
  563. {output_handler,
  564. fun(Line, Acc) ->
  565. %% Line already has a newline so don't use ?CONSOLE which adds one
  566. io:format("~ts", [Line]),
  567. [Line | Acc]
  568. end};
  569. expand_sh_flag({use_stdout, false}) ->
  570. {output_handler,
  571. fun(Line, Acc) ->
  572. [Line | Acc]
  573. end};
  574. expand_sh_flag({cd, _CdArg} = Cd) ->
  575. {port_settings, Cd};
  576. expand_sh_flag({env, _EnvArg} = Env) ->
  577. {port_settings, Env}.
  578. -type err_handler() :: fun((string(), {integer(), string()}) -> no_return()).
  579. -spec log_msg_and_abort(string()) -> err_handler().
  580. log_msg_and_abort(Message) ->
  581. fun(_Command, {_Rc, _Output}) ->
  582. ?ABORT(Message, [])
  583. end.
  584. -spec debug_log_msg_and_abort(string()) -> err_handler().
  585. debug_log_msg_and_abort(Message) ->
  586. fun(Command, {Rc, Output}) ->
  587. ?DEBUG("sh(~ts)~n"
  588. "failed with return code ~w and the following output:~n"
  589. "~ts", [Command, Rc, Output]),
  590. ?ABORT(Message, [])
  591. end.
  592. -spec log_and_abort(string(), {integer(), string()}) -> no_return().
  593. log_and_abort(Command, {Rc, Output}) ->
  594. ?ABORT("sh(~ts)~n"
  595. "failed with return code ~w and the following output:~n"
  596. "~ts", [Command, Rc, Output]).
  597. -spec debug_and_abort(string(), {integer(), string()}) -> no_return().
  598. debug_and_abort(Command, {Rc, Output}) ->
  599. ?DEBUG("sh(~ts)~n"
  600. "failed with return code ~w and the following output:~n"
  601. "~ts", [Command, Rc, Output]),
  602. throw(rebar_abort).
  603. sh_loop(Port, Fun, Acc) ->
  604. receive
  605. {Port, {data, {eol, Line}}} ->
  606. sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
  607. {Port, {data, {noeol, Line}}} ->
  608. sh_loop(Port, Fun, Fun(Line, Acc));
  609. {Port, eof} ->
  610. Data = lists:flatten(lists:reverse(Acc)),
  611. receive
  612. {Port, {exit_status, 0}} ->
  613. {ok, Data};
  614. {Port, {exit_status, Rc}} ->
  615. {error, {Rc, Data}}
  616. end
  617. end.
  618. beam_to_mod(Filename) ->
  619. list_to_atom(filename:basename(Filename, ".beam")).
  620. erl_to_mod(Filename) ->
  621. list_to_atom(filename:rootname(filename:basename(Filename))).
  622. beams(Dir) ->
  623. filelib:wildcard(filename:join(Dir, "*.beam")).
  624. -spec abort() -> no_return().
  625. abort() ->
  626. throw(rebar_abort).
  627. -spec abort(string(), [term()]) -> no_return().
  628. abort(String, Args) ->
  629. ?ERROR(String, Args),
  630. abort().
  631. escript_foldl(Fun, Acc, File) ->
  632. case escript:extract(File, [compile_source]) of
  633. {ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
  634. case Body of
  635. {source, BeamCode} ->
  636. GetInfo = fun() -> file:read_file_info(File) end,
  637. GetBin = fun() -> BeamCode end,
  638. {ok, Fun(".", GetInfo, GetBin, Acc)};
  639. {beam, BeamCode} ->
  640. GetInfo = fun() -> file:read_file_info(File) end,
  641. GetBin = fun() -> BeamCode end,
  642. {ok, Fun(".", GetInfo, GetBin, Acc)};
  643. {archive, ArchiveBin} ->
  644. zip:foldl(Fun, Acc, {File, ArchiveBin})
  645. end;
  646. {error, _} = Error ->
  647. Error
  648. end.
  649. %% TODO: this is just for rebar3_hex and maybe other plugins
  650. %% but eventually it should be dropped
  651. vcs_vsn(OriginalVsn, Dir, Resources) when is_list(Dir) ,
  652. is_list(Resources) ->
  653. ?WARN("Using deprecated rebar_utils:vcs_vsn/3. Please upgrade your plugins.", []),
  654. FakeState = rebar_state:new(),
  655. {ok, AppInfo} = rebar_app_info:new(fake, OriginalVsn, Dir),
  656. vcs_vsn(AppInfo, OriginalVsn,
  657. rebar_state:set_resources(FakeState, Resources));
  658. vcs_vsn(AppInfo, Vcs, State) ->
  659. case vcs_vsn_cmd(AppInfo, Vcs, State) of
  660. {plain, VsnString} ->
  661. VsnString;
  662. {cmd, CmdString} ->
  663. cmd_vsn_invoke(CmdString, rebar_app_info:dir(AppInfo));
  664. unknown ->
  665. ?ABORT("vcs_vsn: Unknown vsn format: ~p", [Vcs]);
  666. {error, Reason} ->
  667. ?ABORT("vcs_vsn: ~ts", [Reason])
  668. end.
  669. %% Temp work around for repos like relx that use "semver"
  670. vcs_vsn_cmd(_, Vsn, _) when is_binary(Vsn) ->
  671. {plain, Vsn};
  672. vcs_vsn_cmd(AppInfo, VCS, State) when VCS =:= semver ; VCS =:= "semver" ->
  673. vcs_vsn_cmd(AppInfo, git, State);
  674. vcs_vsn_cmd(_AppInfo, {cmd, _Cmd}=Custom, _) ->
  675. Custom;
  676. vcs_vsn_cmd(AppInfo, {file, File}, _) ->
  677. Path = filename:join(rebar_app_info:dir(AppInfo), File),
  678. {ok, Vsn} = file:read_file(Path),
  679. {plain, to_list(rebar_string:trim(Vsn))};
  680. vcs_vsn_cmd(AppInfo, VCS, State) when is_atom(VCS) ->
  681. rebar_resource_v2:make_vsn(AppInfo, VCS, State);
  682. vcs_vsn_cmd(AppInfo, {VCS, _}=V, State) when is_atom(VCS) ->
  683. rebar_resource_v2:make_vsn(AppInfo, V, State);
  684. vcs_vsn_cmd(AppInfo, VCS, State) when is_list(VCS) ->
  685. try list_to_existing_atom(VCS) of
  686. AVCS ->
  687. case vcs_vsn_cmd(AppInfo, AVCS, State) of
  688. unknown -> {plain, VCS};
  689. Other -> Other
  690. end
  691. catch
  692. error:badarg ->
  693. {plain, VCS}
  694. end;
  695. vcs_vsn_cmd(_, _, _) ->
  696. unknown.
  697. cmd_vsn_invoke(Cmd, Dir) ->
  698. {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
  699. rebar_string:trim(VsnString, trailing, "\n").
  700. %% @doc ident to the level specified
  701. -spec indent(non_neg_integer()) -> iolist().
  702. indent(Amount) when erlang:is_integer(Amount) ->
  703. [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
  704. %% Replace code paths with new paths for existing apps and
  705. %% purge code of the old modules from those apps.
  706. update_code(Paths) -> update_code(Paths, []).
  707. update_code(Paths, Opts) ->
  708. lists:foreach(fun(Path) ->
  709. Name = filename:basename(Path, "/ebin"),
  710. App = list_to_atom(Name),
  711. application:load(App),
  712. case application:get_key(App, modules) of
  713. undefined ->
  714. code:add_patha(Path),
  715. ok;
  716. {ok, Modules} ->
  717. %% replace_path causes problems when running
  718. %% tests in projects like erlware_commons that rebar3
  719. %% also includes
  720. %code:replace_path(App, Path),
  721. code:del_path(App),
  722. code:add_patha(Path),
  723. case lists:member(soft_purge, Opts) of
  724. true ->
  725. [begin code:soft_purge(M), code:delete(M) end || M <- Modules];
  726. false ->
  727. [begin code:purge(M), code:delete(M) end || M <- Modules]
  728. end
  729. end
  730. end, Paths).
  731. remove_from_code_path(Paths) ->
  732. lists:foreach(fun(Path) ->
  733. Name = filename:basename(Path, "/ebin"),
  734. App = list_to_atom(Name),
  735. application:load(App),
  736. case application:get_key(App, modules) of
  737. undefined ->
  738. application:unload(App),
  739. ok;
  740. {ok, Modules} ->
  741. application:unload(App),
  742. [case erlang:check_process_code(self(), M) of
  743. false ->
  744. code:purge(M), code:delete(M);
  745. _ ->
  746. ?DEBUG("~p can't purge ~p safely, doing a soft purge", [self(), M]),
  747. code:soft_purge(M) andalso code:delete(M)
  748. end || M <- Modules]
  749. end,
  750. code:del_path(Path)
  751. end, lists:usort(Paths)).
  752. %% @doc Revert to only having the beams necessary for running rebar3 and
  753. %% plugins in the path
  754. -spec cleanup_code_path([string()]) -> true | {error, term()}.
  755. cleanup_code_path(OrigPath) ->
  756. CurrentPath = code:get_path(),
  757. AddedPaths = CurrentPath -- OrigPath,
  758. %% If someone has removed paths, it's hard to get them back into
  759. %% the right order, but since this is currently rare, we can just
  760. %% fall back to code:set_path/1.
  761. case CurrentPath -- AddedPaths of
  762. OrigPath ->
  763. _ = [code:del_path(Path) || Path <- AddedPaths],
  764. true;
  765. _ ->
  766. code:set_path(OrigPath)
  767. end.
  768. new_task([], Acc) -> lists:reverse(Acc);
  769. new_task([TaskList|Rest], Acc) ->
  770. case re:split(TaskList, ",", [{return, list}, {parts, 2}, unicode]) of
  771. %% `do` consumes all remaining args
  772. ["do" = Task] ->
  773. lists:reverse([{Task, Rest}|Acc]);
  774. %% single task terminated by a comma
  775. [Task, ""] -> new_task(Rest, [{Task, []}|Acc]);
  776. %% sequence of two or more tasks
  777. [Task, More] -> new_task([More|Rest], [{Task, []}|Acc]);
  778. %% single task not terminated by a comma
  779. [Task] -> arg_or_flag(Rest, [{Task, []}|Acc])
  780. end.
  781. arg_or_flag([], [{Task, Args}|Acc]) ->
  782. lists:reverse([{Task, lists:reverse(Args)}|Acc]);
  783. %% case where you have `foo , bar`
  784. arg_or_flag([","|Rest], Acc) -> new_task(Rest, Acc);
  785. %% case where you have `foo ,bar`
  786. arg_or_flag(["," ++ Task|Rest], Acc) -> new_task([Task|Rest], Acc);
  787. %% a flag
  788. arg_or_flag(["-" ++ _ = Flag|Rest], [{Task, Args}|Acc]) ->
  789. case maybe_ends_in_comma(Flag) of
  790. false -> arg_or_flag(Rest, [{Task, [Flag|Args]}|Acc]);
  791. NewFlag -> new_task(Rest, [{Task,
  792. lists:reverse([NewFlag|Args])}|Acc])
  793. end;
  794. %% an argument or a sequence of arguments
  795. arg_or_flag([ArgList|Rest], [{Task, Args}|Acc]) ->
  796. case re:split(ArgList, ",", [{return, list}, {parts, 2}, unicode]) of
  797. %% single arg terminated by a comma
  798. [Arg, ""] -> new_task(Rest, [{Task,
  799. lists:reverse([Arg|Args])}|Acc]);
  800. %% sequence of two or more args/tasks
  801. [Arg, More] -> new_task([More|Rest], [{Task,
  802. lists:reverse([Arg|Args])}|Acc]);
  803. %% single arg not terminated by a comma
  804. [Arg] -> arg_or_flag(Rest, [{Task, [Arg|Args]}|Acc])
  805. end.
  806. maybe_ends_in_comma(H) ->
  807. case lists:reverse(H) of
  808. "," ++ Flag -> lists:reverse(Flag);
  809. _ -> false
  810. end.
  811. get_http_vars(Scheme) ->
  812. OS = case os:getenv(atom_to_list(Scheme)) of
  813. Str when is_list(Str) -> Str;
  814. _ -> []
  815. end,
  816. GlobalConfigFile = rebar_dir:global_config(),
  817. Config = rebar_config:consult_file(GlobalConfigFile),
  818. proplists:get_value(Scheme, Config, OS).
  819. set_httpc_options() ->
  820. set_httpc_options(https_proxy, get_http_vars(https_proxy)),
  821. set_httpc_options(proxy, get_http_vars(http_proxy)).
  822. set_httpc_options(_, []) ->
  823. ok;
  824. set_httpc_options(Scheme, Proxy) ->
  825. URI = normalise_proxy(Scheme, Proxy),
  826. {ok, {_, UserInfo, Host, Port, _, _}} = http_uri:parse(URI),
  827. httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar),
  828. set_proxy_auth(UserInfo).
  829. normalise_proxy(Scheme, URI) ->
  830. case re:run(URI, "://", [unicode]) of
  831. nomatch when Scheme =:= https_proxy -> "https://" ++ URI;
  832. nomatch when Scheme =:= proxy -> "http://" ++ URI;
  833. _ -> URI
  834. end.
  835. url_append_path(Url, ExtraPath) ->
  836. case http_uri:parse(Url) of
  837. {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
  838. {ok, lists:append([atom_to_list(Scheme), "://", UserInfo, Host, ":", integer_to_list(Port),
  839. filename:join(Path, ExtraPath), Query])};
  840. _ ->
  841. error
  842. end.
  843. %% escape\ as\ a\ shell\?
  844. escape_chars(Str) when is_atom(Str) ->
  845. escape_chars(atom_to_list(Str));
  846. escape_chars(Str) ->
  847. re:replace(Str, "([ ()?`!$&;\"\'])", "\\\\&",
  848. [global, {return, list}, unicode]).
  849. %% "escape inside these"
  850. escape_double_quotes(Str) ->
  851. re:replace(Str, "([\"\\\\`!$&*;])", "\\\\&",
  852. [global, {return, list}, unicode]).
  853. %% "escape inside these" but allow *
  854. escape_double_quotes_weak(Str) ->
  855. re:replace(Str, "([\"\\\\`!$&;])", "\\\\&",
  856. [global, {return, list}, unicode]).
  857. info_useless(Old, New) ->
  858. [?INFO("App ~ts is no longer needed and can be deleted.", [Name])
  859. || Name <- Old,
  860. not lists:member(Name, New)],
  861. ok.
  862. list_dir(Dir) ->
  863. %% `list_dir_all` returns raw files which are unsupported
  864. %% prior to R16 so just fall back to `list_dir` if running
  865. %% on an earlier vm
  866. case erlang:function_exported(file, list_dir_all, 1) of
  867. true -> file:list_dir_all(Dir);
  868. false -> file:list_dir(Dir)
  869. end.
  870. set_proxy_auth([]) ->
  871. ok;
  872. set_proxy_auth(UserInfo) ->
  873. [Username, Password] = re:split(UserInfo, ":",
  874. [{return, list}, {parts,2}, unicode]),
  875. %% password may contain url encoded characters, need to decode them first
  876. application:set_env(rebar, proxy_auth, [{proxy_auth, {Username, http_uri:decode(Password)}}]).
  877. get_proxy_auth() ->
  878. case application:get_env(rebar, proxy_auth) of
  879. undefined -> [];
  880. {ok, ProxyAuth} -> ProxyAuth
  881. end.
  882. -spec is_list_of_strings(term()) -> boolean().
  883. is_list_of_strings(List) when not is_list(hd(List)) ->
  884. false;
  885. is_list_of_strings(List) when is_list(hd(List)) ->
  886. true;
  887. is_list_of_strings(List) when is_list(List) ->
  888. true.
  889. %%------------------------------------------------------------------------------
  890. %% @doc
  891. %% Return the SSL options adequate for the project based on
  892. %% its configuration, including for validation of certs.
  893. %% @end
  894. %%------------------------------------------------------------------------------
  895. -spec ssl_opts(Url) -> Res when
  896. Url :: string(),
  897. Res :: proplists:proplist().
  898. ssl_opts(Url) ->
  899. case get_ssl_config() of
  900. ssl_verify_enabled ->
  901. ssl_opts(ssl_verify_enabled, Url);
  902. ssl_verify_disabled ->
  903. [{verify, verify_none}]
  904. end.
  905. %%------------------------------------------------------------------------------
  906. %% @doc
  907. %% Return the SSL options adequate for the project based on
  908. %% its configuration, including for validation of certs.
  909. %% @end
  910. %%------------------------------------------------------------------------------
  911. -spec ssl_opts(Enabled, Url) -> Res when
  912. Enabled :: atom(),
  913. Url :: string(),
  914. Res :: proplists:proplist().
  915. ssl_opts(ssl_verify_enabled, Url) ->
  916. case check_ssl_version() of
  917. true ->
  918. {ok, {_, _, Hostname, _, _, _}} =
  919. http_uri:parse(rebar_utils:to_list(Url)),
  920. VerifyFun = {fun ssl_verify_hostname:verify_fun/3,
  921. [{check_hostname, Hostname}]},
  922. CACerts = certifi:cacerts(),
  923. [{verify, verify_peer}, {depth, 2}, {cacerts, CACerts},
  924. {partial_chain, fun partial_chain/1}, {verify_fun, VerifyFun}];
  925. false ->
  926. ?WARN("Insecure HTTPS request (peer verification disabled), "
  927. "please update to OTP 17.4 or later", []),
  928. [{verify, verify_none}]
  929. end.
  930. -spec partial_chain(Certs) -> Res when
  931. Certs :: list(any()),
  932. Res :: unknown_ca | {trusted_ca, any()}.
  933. partial_chain(Certs) ->
  934. Certs1 = [{Cert, public_key:pkix_decode_cert(Cert, otp)} || Cert <- Certs],
  935. CACerts = certifi:cacerts(),
  936. CACerts1 = [public_key:pkix_decode_cert(Cert, otp) || Cert <- CACerts],
  937. case ec_lists:find(fun({_, Cert}) ->
  938. check_cert(CACerts1, Cert)
  939. end, Certs1) of
  940. {ok, Trusted} ->
  941. {trusted_ca, element(1, Trusted)};
  942. _ ->
  943. unknown_ca
  944. end.
  945. -spec extract_public_key_info(Cert) -> Res when
  946. Cert :: #'OTPCertificate'{tbsCertificate::#'OTPTBSCertificate'{}},
  947. Res :: any().
  948. extract_public_key_info(Cert) ->
  949. ((Cert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subjectPublicKeyInfo).
  950. -spec check_cert(CACerts, Cert) -> Res when
  951. CACerts :: list(any()),
  952. Cert :: any(),
  953. Res :: boolean().
  954. check_cert(CACerts, Cert) ->
  955. lists:any(fun(CACert) ->
  956. extract_public_key_info(CACert) == extract_public_key_info(Cert)
  957. end, CACerts).
  958. -spec check_ssl_version() ->
  959. boolean().
  960. check_ssl_version() ->
  961. case application:get_key(ssl, vsn) of
  962. {ok, Vsn} ->
  963. parse_vsn(Vsn) >= {5, 3, 6};
  964. _ ->
  965. false
  966. end.
  967. -spec get_ssl_config() ->
  968. ssl_verify_disabled | ssl_verify_enabled.
  969. get_ssl_config() ->
  970. GlobalConfigFile = rebar_dir:global_config(),
  971. Config = rebar_config:consult_file(GlobalConfigFile),
  972. case proplists:get_value(ssl_verify, Config, []) of
  973. false ->
  974. ssl_verify_disabled;
  975. _ ->
  976. ssl_verify_enabled
  977. end.
  978. -spec parse_vsn(Vsn) -> Res when
  979. Vsn :: string(),
  980. Res :: {integer(), integer(), integer()}.
  981. parse_vsn(Vsn) ->
  982. version_pad(rebar_string:lexemes(Vsn, ".-")).
  983. -spec version_pad(list(nonempty_string())) -> Res when
  984. Res :: {integer(), integer(), integer()}.
  985. version_pad([Major]) ->
  986. {list_to_integer(Major), 0, 0};
  987. version_pad([Major, Minor]) ->
  988. {list_to_integer(Major), list_to_integer(Minor), 0};
  989. version_pad([Major, Minor, Patch]) ->
  990. {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)};
  991. version_pad([Major, Minor, Patch | _]) ->
  992. {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.
  993. -ifdef(filelib_find_source).
  994. find_source(Filename, Dir, Rules) ->
  995. filelib:find_source(Filename, Dir, Rules).
  996. -else.
  997. %% Looks for a file relative to a given directory
  998. -type find_file_rule() :: {ObjDirSuffix::string(), SrcDirSuffix::string()}.
  999. %% Looks for a source file relative to the object file name and directory
  1000. -type find_source_rule() :: {ObjExtension::string(), SrcExtension::string(),
  1001. [find_file_rule()]}.
  1002. keep_suffix_search_rules(Rules) ->
  1003. [T || {_,_,_}=T <- Rules].
  1004. -spec find_source(file:filename(), file:filename(), [find_source_rule()]) ->
  1005. {ok, file:filename()} | {error, not_found}.
  1006. find_source(Filename, Dir, Rules) ->
  1007. try_suffix_rules(keep_suffix_search_rules(Rules), Filename, Dir).
  1008. try_suffix_rules(Rules, Filename, Dir) ->
  1009. Ext = filename:extension(Filename),
  1010. try_suffix_rules(Rules, filename:rootname(Filename, Ext), Dir, Ext).
  1011. try_suffix_rules([{Ext,Src,Rules}|Rest], Root, Dir, Ext)
  1012. when is_list(Src), is_list(Rules) ->
  1013. case try_dir_rules(add_local_search(Rules), Root ++ Src, Dir) of
  1014. {ok, File} -> {ok, File};
  1015. _Other ->
  1016. try_suffix_rules(Rest, Root, Dir, Ext)
  1017. end;
  1018. try_suffix_rules([_|Rest], Root, Dir, Ext) ->
  1019. try_suffix_rules(Rest, Root, Dir, Ext);
  1020. try_suffix_rules([], _Root, _Dir, _Ext) ->
  1021. {error, not_found}.
  1022. %% ensuring we check the directory of the object file before any other directory
  1023. add_local_search(Rules) ->
  1024. Local = {"",""},
  1025. [Local] ++ lists:filter(fun (X) -> X =/= Local end, Rules).
  1026. try_dir_rules([{From, To}|Rest], Filename, Dir)
  1027. when is_list(From), is_list(To) ->
  1028. case try_dir_rule(Dir, Filename, From, To) of
  1029. {ok, File} -> {ok, File};
  1030. error -> try_dir_rules(Rest, Filename, Dir)
  1031. end;
  1032. try_dir_rules([], _Filename, _Dir) ->
  1033. {error, not_found}.
  1034. try_dir_rule(Dir, Filename, From, To) ->
  1035. case lists:suffix(From, Dir) of
  1036. true ->
  1037. NewDir = lists:sublist(Dir, 1, length(Dir)-length(From))++To,
  1038. Src = filename:join(NewDir, Filename),
  1039. case filelib:is_regular(Src) of
  1040. true -> {ok, Src};
  1041. false -> find_regular_file(filelib:wildcard(Src))
  1042. end;
  1043. false ->
  1044. error
  1045. end.
  1046. find_regular_file([]) ->
  1047. error;
  1048. find_regular_file([File|Files]) ->
  1049. case filelib:is_regular(File) of
  1050. true -> {ok, File};
  1051. false -> find_regular_file(Files)
  1052. end.
  1053. -endif.