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.

695 lines
25 KiB

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 years ago
6 years ago
7 years ago
10 years ago
10 years ago
10 years ago
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 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. #!/usr/bin/env escript
  2. %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
  3. %% ex: ft=erlang ts=4 sw=4 et
  4. main(_) ->
  5. application:start(crypto),
  6. application:start(asn1),
  7. application:start(public_key),
  8. application:start(ssl),
  9. inets:start(),
  10. inets:start(httpc, [{profile, rebar}]),
  11. set_httpc_options(),
  12. %% Clear directories for builds since bootstrapping may require
  13. %% a changed structure from an older one
  14. rm_rf("_build/bootstrap"),
  15. %% Fetch and build deps required to build rebar3
  16. BaseDeps = [{providers, []}
  17. ,{getopt, []}
  18. ,{cf, []}
  19. ,{erlware_commons, ["ec_dictionary.erl", "ec_vsn.erl"]}
  20. ,{parse_trans, ["parse_trans.erl", "parse_trans_pp.erl",
  21. "parse_trans_codegen.erl"]}
  22. ,{certifi, []}
  23. ,{hex_core, []}],
  24. Deps = get_deps(),
  25. [fetch_and_compile(Dep, Deps) || Dep <- BaseDeps],
  26. %% Build rebar3 modules with compile:file
  27. bootstrap_rebar3(),
  28. %% Build rebar.app from rebar.app.src
  29. {ok, App} = rebar_app_info:new(rebar, "3.9.0", filename:absname("_build/default/lib/rebar/")),
  30. rebar_otp_app:compile(rebar_state:new(), App),
  31. %% Because we are compiling files that are loaded already we want to silence
  32. %% not_purged errors in rebar_erlc_compiler:opts_changed/1
  33. error_logger:tty(false),
  34. setup_env(),
  35. os:putenv("REBAR_PROFILE", "bootstrap"),
  36. {ok, State} = rebar3:run(["compile"]),
  37. reset_env(),
  38. os:putenv("REBAR_PROFILE", ""),
  39. DepsPaths = rebar_state:code_paths(State, all_deps),
  40. code:add_pathsa(DepsPaths),
  41. rebar3:run(["clean", "-a"]),
  42. rebar3:run(["as", "prod", "escriptize"]),
  43. %% Done with compile, can turn back on error logger
  44. error_logger:tty(true).
  45. fetch_and_compile({Name, ErlFirstFiles}, Deps) ->
  46. case lists:keyfind(Name, 1, Deps) of
  47. {Name, Vsn} ->
  48. ok = fetch({pkg, atom_to_binary(Name, utf8), list_to_binary(Vsn)}, Name);
  49. {Name, _, Source} ->
  50. ok = fetch(Source, Name)
  51. end,
  52. %% Hack: erlware_commons depends on a .script file to check if it is being built with
  53. %% rebar2 or rebar3. But since rebar3 isn't built yet it can't get the vsn with get_key.
  54. %% So we simply make sure that file is deleted before compiling
  55. file:delete("_build/default/lib/erlware_commons/rebar.config.script"),
  56. compile(Name, ErlFirstFiles).
  57. fetch({pkg, Name, Vsn}, App) ->
  58. Dir = filename:join([filename:absname("_build/default/lib/"), App]),
  59. case filelib:is_dir(Dir) of
  60. false ->
  61. CDN = "https://repo.hex.pm/tarballs",
  62. Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
  63. Url = join([CDN, Package], "/"),
  64. case request(Url) of
  65. {ok, Binary} ->
  66. {ok, Contents} = extract(Binary),
  67. ok = erl_tar:extract({binary, Contents}, [{cwd, Dir}, compressed]);
  68. {error, {Reason, _}} ->
  69. ReasonText = re:replace(atom_to_list(Reason), "_", " ", [global,{return,list}]),
  70. io:format("Error: Unable to fetch package ~s ~s: ~s~n", [Name, Vsn, ReasonText])
  71. end;
  72. true ->
  73. io:format("Dependency ~s already exists~n", [Name])
  74. end.
  75. extract(Binary) ->
  76. {ok, Files} = erl_tar:extract({binary, Binary}, [memory]),
  77. {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files),
  78. {ok, Contents}.
  79. request(Url) ->
  80. HttpOptions = [{relaxed, true} | get_proxy_auth()],
  81. case httpc:request(get, {Url, []},
  82. HttpOptions,
  83. [{body_format, binary}],
  84. rebar) of
  85. {ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
  86. {ok, Body};
  87. Error ->
  88. Error
  89. end.
  90. get_rebar_config() ->
  91. {ok, [[Home]]} = init:get_argument(home),
  92. ConfDir = filename:join(Home, ".config/rebar3"),
  93. case file:consult(filename:join(ConfDir, "rebar.config")) of
  94. {ok, Config} ->
  95. Config;
  96. _ ->
  97. []
  98. end.
  99. get_http_vars(Scheme) ->
  100. OS = case os:getenv(atom_to_list(Scheme)) of
  101. Str when is_list(Str) -> Str;
  102. _ -> []
  103. end,
  104. proplists:get_value(Scheme, get_rebar_config(), OS).
  105. set_httpc_options() ->
  106. set_httpc_options(https_proxy, get_http_vars(https_proxy)),
  107. set_httpc_options(proxy, get_http_vars(http_proxy)).
  108. set_httpc_options(_, []) ->
  109. ok;
  110. set_httpc_options(Scheme, Proxy) ->
  111. {ok, {_, UserInfo, Host, Port, _, _}} = http_uri:parse(Proxy),
  112. httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar),
  113. set_proxy_auth(UserInfo).
  114. compile(App, FirstFiles) ->
  115. Dir = filename:join(filename:absname("_build/default/lib/"), App),
  116. filelib:ensure_dir(filename:join([Dir, "ebin", "dummy.beam"])),
  117. code:add_path(filename:join(Dir, "ebin")),
  118. FirstFilesPaths = [filename:join([Dir, "src", Module]) || Module <- FirstFiles],
  119. LeexFiles = filelib:wildcard(filename:join([Dir, "src", "*.xrl"])),
  120. [compile_xrl_file(X) || X <- LeexFiles],
  121. YeccFiles = filelib:wildcard(filename:join([Dir, "src", "*.yrl"])),
  122. [compile_yrl_file(X) || X <- YeccFiles],
  123. Sources = FirstFilesPaths ++ filelib:wildcard(filename:join([Dir, "src", "*.erl"])),
  124. [compile_erl_file(X, [{i, filename:join(Dir, "include")}
  125. ,debug_info
  126. ,{outdir, filename:join(Dir, "ebin")}
  127. ,return | additional_defines()]) || X <- Sources].
  128. compile_xrl_file(File) ->
  129. {ok, _} = leex:file(File).
  130. compile_yrl_file(File) ->
  131. {ok, _} = yecc:file(File).
  132. compile_erl_file(File, Opts) ->
  133. case compile:file(File, Opts) of
  134. {ok, _Mod} ->
  135. ok;
  136. {ok, _Mod, []} ->
  137. ok;
  138. {ok, _Mod, Ws} ->
  139. io:format("~s~n", [format_warnings(File, Ws)]),
  140. halt(1);
  141. {error, Es, Ws} ->
  142. io:format("~s ~s~n", [format_errors(File, Es), format_warnings(File, Ws)]),
  143. halt(1)
  144. end.
  145. bootstrap_rebar3() ->
  146. filelib:ensure_dir("_build/default/lib/rebar/ebin/dummy.beam"),
  147. code:add_path("_build/default/lib/rebar/ebin/"),
  148. Res = symlink_or_copy(filename:absname("src"),
  149. filename:absname("_build/default/lib/rebar/src")),
  150. true = Res == ok orelse Res == exists,
  151. Sources = ["src/rebar_resource_v2.erl", "src/rebar_resource.erl" | filelib:wildcard("src/*.erl")],
  152. [compile_erl_file(X, [{outdir, "_build/default/lib/rebar/ebin/"}
  153. ,return | additional_defines()]) || X <- Sources],
  154. code:add_patha(filename:absname("_build/default/lib/rebar/ebin")).
  155. %%rebar.hrl
  156. -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
  157. %%/rebar.hrl
  158. %%rebar_file_utils
  159. -include_lib("kernel/include/file.hrl").
  160. symlink_or_copy(Source, Target) ->
  161. Link = case os:type() of
  162. {win32, _} ->
  163. Source;
  164. _ ->
  165. make_relative_path(Source, Target)
  166. end,
  167. case file:make_symlink(Link, Target) of
  168. ok ->
  169. ok;
  170. {error, eexist} ->
  171. exists;
  172. {error, _} ->
  173. case os:type() of
  174. {win32, _} ->
  175. S = unicode:characters_to_list(Source),
  176. T = unicode:characters_to_list(Target),
  177. case filelib:is_dir(S) of
  178. true ->
  179. win32_symlink_or_copy(S, T);
  180. false ->
  181. cp_r([S], T)
  182. end;
  183. _ ->
  184. case filelib:is_dir(Target) of
  185. true ->
  186. ok;
  187. false ->
  188. cp_r([Source], Target)
  189. end
  190. end
  191. end.
  192. -spec rm_rf(string()) -> 'ok'.
  193. rm_rf(Target) ->
  194. case os:type() of
  195. {unix, _} ->
  196. EscTarget = escape_chars(Target),
  197. {ok, []} = sh(?FMT("rm -rf ~ts", [EscTarget]),
  198. [{use_stdout, false}, abort_on_error]),
  199. ok;
  200. {win32, _} ->
  201. Filelist = filelib:wildcard(Target),
  202. Dirs = [F || F <- Filelist, filelib:is_dir(F)],
  203. Files = Filelist -- Dirs,
  204. ok = delete_each(Files),
  205. ok = delete_each_dir_win32(Dirs),
  206. ok
  207. end.
  208. -spec cp_r(list(string()), file:filename()) -> 'ok'.
  209. cp_r([], _Dest) ->
  210. ok;
  211. cp_r(Sources, Dest) ->
  212. case os:type() of
  213. {unix, _} ->
  214. EscSources = [escape_chars(Src) || Src <- Sources],
  215. SourceStr = join(EscSources, " "),
  216. {ok, []} = sh(?FMT("cp -Rp ~ts \"~ts\"",
  217. [SourceStr, escape_double_quotes(Dest)]),
  218. [{use_stdout, false}, abort_on_error]),
  219. ok;
  220. {win32, _} ->
  221. lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
  222. ok
  223. end.
  224. %% @private Compatibility function for windows
  225. win32_symlink_or_copy(Source, Target) ->
  226. Res = sh(?FMT("cmd /c mklink /j \"~ts\" \"~ts\"",
  227. [escape_double_quotes(filename:nativename(Target)),
  228. escape_double_quotes(filename:nativename(Source))]),
  229. [{use_stdout, false}, return_on_error]),
  230. case win32_mklink_ok(Res, Target) of
  231. true -> ok;
  232. false -> cp_r_win32(Source, drop_last_dir_from_path(Target))
  233. end.
  234. cp_r_win32({true, SourceDir}, {true, DestDir}) ->
  235. %% from directory to directory
  236. ok = case file:make_dir(DestDir) of
  237. {error, eexist} -> ok;
  238. Other -> Other
  239. end,
  240. ok = xcopy_win32(SourceDir, DestDir);
  241. cp_r_win32({false, Source} = S,{true, DestDir}) ->
  242. %% from file to directory
  243. cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
  244. cp_r_win32({false, Source},{false, Dest}) ->
  245. %% from file to file
  246. {ok,_} = file:copy(Source, Dest),
  247. ok;
  248. cp_r_win32({true, SourceDir}, {false, DestDir}) ->
  249. case filelib:is_regular(DestDir) of
  250. true ->
  251. %% From directory to file? This shouldn't happen
  252. {error, lists:flatten(
  253. io_lib:format("Cannot copy dir (~p) to file (~p)\n",
  254. [SourceDir, DestDir]))};
  255. false ->
  256. %% Specifying a target directory that doesn't currently exist.
  257. %% So let's attempt to create this directory
  258. case filelib:ensure_dir(filename:join(DestDir, "dummy")) of
  259. ok ->
  260. ok = xcopy_win32(SourceDir, DestDir);
  261. {error, Reason} ->
  262. {error, lists:flatten(
  263. io_lib:format("Unable to create dir ~p: ~p\n",
  264. [DestDir, Reason]))}
  265. end
  266. end;
  267. cp_r_win32(Source,Dest) ->
  268. Dst = {filelib:is_dir(Dest), Dest},
  269. lists:foreach(fun(Src) ->
  270. ok = cp_r_win32({filelib:is_dir(Src), Src}, Dst)
  271. end, filelib:wildcard(Source)),
  272. ok.
  273. %% drops the last 'node' of the filename, presumably the last dir such as 'src'
  274. %% this is because cp_r_win32/2 automatically adds the dir name, to appease
  275. %% robocopy and be more uniform with POSIX
  276. drop_last_dir_from_path([]) ->
  277. [];
  278. drop_last_dir_from_path(Path) ->
  279. case lists:droplast(filename:split(Path)) of
  280. [] -> [];
  281. Dirs -> filename:join(Dirs)
  282. end.
  283. %% @private specifically pattern match against the output
  284. %% of the windows 'mklink' shell call; different values from
  285. %% what win32_ok/1 handles
  286. win32_mklink_ok({ok, _}, _) ->
  287. true;
  288. win32_mklink_ok({error,{1,"Local NTFS volumes are required to complete the operation.\n"}}, _) ->
  289. false;
  290. win32_mklink_ok({error,{1,"Cannot create a file when that file already exists.\n"}}, Target) ->
  291. % File or dir is already in place; find if it is already a symlink (true) or
  292. % if it is a directory (copy-required; false)
  293. is_symlink(Target);
  294. win32_mklink_ok(_, _) ->
  295. false.
  296. xcopy_win32(Source,Dest)->
  297. %% "xcopy \"~ts\" \"~ts\" /q /y /e 2> nul", Changed to robocopy to
  298. %% handle long names. May have issues with older windows.
  299. Cmd = case filelib:is_dir(Source) of
  300. true ->
  301. %% For robocopy, copying /a/b/c/ to /d/e/f/ recursively does not
  302. %% create /d/e/f/c/*, but rather copies all files to /d/e/f/*.
  303. %% The usage we make here expects the former, not the later, so we
  304. %% must manually add the last fragment of a directory to the `Dest`
  305. %% in order to properly replicate POSIX platforms
  306. NewDest = filename:join([Dest, filename:basename(Source)]),
  307. ?FMT("robocopy \"~ts\" \"~ts\" /e 1> nul",
  308. [escape_double_quotes(filename:nativename(Source)),
  309. escape_double_quotes(filename:nativename(NewDest))]);
  310. false ->
  311. ?FMT("robocopy \"~ts\" \"~ts\" \"~ts\" /e 1> nul",
  312. [escape_double_quotes(filename:nativename(filename:dirname(Source))),
  313. escape_double_quotes(filename:nativename(Dest)),
  314. escape_double_quotes(filename:basename(Source))])
  315. end,
  316. Res = sh(Cmd, [{use_stdout, false}, return_on_error]),
  317. case win32_ok(Res) of
  318. true -> ok;
  319. false ->
  320. {error, lists:flatten(
  321. io_lib:format("Failed to copy ~ts to ~ts~n",
  322. [Source, Dest]))}
  323. end.
  324. is_symlink(Filename) ->
  325. {ok, Info} = file:read_link_info(Filename),
  326. Info#file_info.type == symlink.
  327. win32_ok({ok, _}) -> true;
  328. win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true;
  329. win32_ok(_) -> false.
  330. %% @private windows rm_rf helpers
  331. delete_each([]) ->
  332. ok;
  333. delete_each([File | Rest]) ->
  334. case file:delete(File) of
  335. ok ->
  336. delete_each(Rest);
  337. {error, enoent} ->
  338. delete_each(Rest);
  339. {error, Reason} ->
  340. io:format("Failed to delete file ~ts: ~p\n", [File, Reason]),
  341. error
  342. end.
  343. delete_each_dir_win32([]) -> ok;
  344. delete_each_dir_win32([Dir | Rest]) ->
  345. {ok, []} = sh(?FMT("rd /q /s \"~ts\"",
  346. [escape_double_quotes(filename:nativename(Dir))]),
  347. [{use_stdout, false}, return_on_error]),
  348. delete_each_dir_win32(Rest).
  349. %%/rebar_file_utils
  350. %%rebar_utils
  351. %% escape\ as\ a\ shell\?
  352. escape_chars(Str) when is_atom(Str) ->
  353. escape_chars(atom_to_list(Str));
  354. escape_chars(Str) ->
  355. re:replace(Str, "([ ()?`!$&;\"\'])", "\\\\&",
  356. [global, {return, list}, unicode]).
  357. %% "escape inside these"
  358. escape_double_quotes(Str) ->
  359. re:replace(Str, "([\"\\\\`!$&*;])", "\\\\&",
  360. [global, {return, list}, unicode]).
  361. sh(Command0, Options0) ->
  362. DefaultOptions = [{use_stdout, false}],
  363. Options = [expand_sh_flag(V)
  364. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  365. ErrorHandler = proplists:get_value(error_handler, Options),
  366. OutputHandler = proplists:get_value(output_handler, Options),
  367. Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))),
  368. PortSettings = proplists:get_all_values(port_settings, Options) ++
  369. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof],
  370. Port = open_port({spawn, Command}, PortSettings),
  371. try
  372. case sh_loop(Port, OutputHandler, []) of
  373. {ok, _Output} = Ok ->
  374. Ok;
  375. {error, {_Rc, _Output}=Err} ->
  376. ErrorHandler(Command, Err)
  377. end
  378. after
  379. port_close(Port)
  380. end.
  381. sh_loop(Port, Fun, Acc) ->
  382. receive
  383. {Port, {data, {eol, Line}}} ->
  384. sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
  385. {Port, {data, {noeol, Line}}} ->
  386. sh_loop(Port, Fun, Fun(Line, Acc));
  387. {Port, eof} ->
  388. Data = lists:flatten(lists:reverse(Acc)),
  389. receive
  390. {Port, {exit_status, 0}} ->
  391. {ok, Data};
  392. {Port, {exit_status, Rc}} ->
  393. {error, {Rc, Data}}
  394. end
  395. end.
  396. expand_sh_flag(return_on_error) ->
  397. {error_handler,
  398. fun(_Command, Err) ->
  399. {error, Err}
  400. end};
  401. expand_sh_flag(abort_on_error) ->
  402. {error_handler,
  403. %% moved log_and_abort/2 here because some versions somehow had trouble
  404. %% interpreting it and thought `fun log_and_abort/2' was in `erl_eval'
  405. fun(Command, {Rc, Output}) ->
  406. io:format("sh(~ts)~n"
  407. "failed with return code ~w and the following output:~n"
  408. "~ts", [Command, Rc, Output]),
  409. throw(bootstrap_abort)
  410. end};
  411. expand_sh_flag({use_stdout, false}) ->
  412. {output_handler,
  413. fun(Line, Acc) ->
  414. [Line | Acc]
  415. end};
  416. expand_sh_flag({cd, _CdArg} = Cd) ->
  417. {port_settings, Cd};
  418. expand_sh_flag({env, _EnvArg} = Env) ->
  419. {port_settings, Env}.
  420. %% We do the shell variable substitution ourselves on Windows and hope that the
  421. %% command doesn't use any other shell magic.
  422. patch_on_windows(Cmd, Env) ->
  423. case os:type() of
  424. {win32,nt} ->
  425. Cmd1 = "cmd /q /c "
  426. ++ lists:foldl(fun({Key, Value}, Acc) ->
  427. expand_env_variable(Acc, Key, Value)
  428. end, Cmd, Env),
  429. %% Remove left-over vars
  430. re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
  431. [global, {return, list}, unicode]);
  432. _ ->
  433. Cmd
  434. end.
  435. %% @doc Given env. variable `FOO' we want to expand all references to
  436. %% it in `InStr'. References can have two forms: `$FOO' and `${FOO}'
  437. %% The end of form `$FOO' is delimited with whitespace or EOL
  438. -spec expand_env_variable(string(), string(), term()) -> string().
  439. expand_env_variable(InStr, VarName, RawVarValue) ->
  440. case chr(InStr, $$) of
  441. 0 ->
  442. %% No variables to expand
  443. InStr;
  444. _ ->
  445. ReOpts = [global, unicode, {return, list}],
  446. VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
  447. %% Use a regex to match/replace:
  448. %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
  449. RegEx = io_lib:format("\\\$(~ts(\\W|$)|{~ts})", [VarName, VarName]),
  450. re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
  451. end.
  452. %%/rebar_utils
  453. %%rebar_dir
  454. make_relative_path(Source, Target) ->
  455. AbsSource = make_normalized_path(Source),
  456. AbsTarget = make_normalized_path(Target),
  457. do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)).
  458. %% @private based on fragments of paths, replace the number of common
  459. %% segments by `../' bits, and add the rest of the source alone after it
  460. -spec do_make_relative_path([string()], [string()]) -> file:filename().
  461. do_make_relative_path([H|T1], [H|T2]) ->
  462. do_make_relative_path(T1, T2);
  463. do_make_relative_path(Source, Target) ->
  464. Base = lists:duplicate(max(length(Target) - 1, 0), ".."),
  465. filename:join(Base ++ Source).
  466. make_normalized_path(Path) ->
  467. AbsPath = make_absolute_path(Path),
  468. Components = filename:split(AbsPath),
  469. make_normalized_path(Components, []).
  470. make_absolute_path(Path) ->
  471. case filename:pathtype(Path) of
  472. absolute ->
  473. Path;
  474. relative ->
  475. {ok, Dir} = file:get_cwd(),
  476. filename:join([Dir, Path]);
  477. volumerelative ->
  478. Volume = hd(filename:split(Path)),
  479. {ok, Dir} = file:get_cwd(Volume),
  480. filename:join([Dir, Path])
  481. end.
  482. -spec make_normalized_path([string()], [string()]) -> file:filename().
  483. make_normalized_path([], NormalizedPath) ->
  484. filename:join(lists:reverse(NormalizedPath));
  485. make_normalized_path([H|T], NormalizedPath) ->
  486. case H of
  487. "." when NormalizedPath == [], T == [] -> make_normalized_path(T, ["."]);
  488. "." -> make_normalized_path(T, NormalizedPath);
  489. ".." when NormalizedPath == [] -> make_normalized_path(T, [".."]);
  490. ".." when hd(NormalizedPath) =/= ".." -> make_normalized_path(T, tl(NormalizedPath));
  491. _ -> make_normalized_path(T, [H|NormalizedPath])
  492. end.
  493. %%/rebar_dir
  494. setup_env() ->
  495. %% We don't need or want relx providers loaded yet
  496. application:load(rebar),
  497. {ok, Providers} = application:get_env(rebar, providers),
  498. Providers1 = Providers -- [rebar_prv_release,
  499. rebar_prv_relup,
  500. rebar_prv_tar],
  501. application:set_env(rebar, providers, Providers1).
  502. reset_env() ->
  503. %% Reset the env so we get all providers
  504. application:unset_env(rebar, providers),
  505. application:unload(rebar),
  506. application:load(rebar).
  507. get_deps() ->
  508. case file:consult("rebar.lock") of
  509. {ok, [[]]} ->
  510. %% Something went wrong in a previous build, lock file shouldn't be empty
  511. io:format("Empty list in lock file, deleting rebar.lock~n"),
  512. ok = file:delete("rebar.lock"),
  513. {ok, Config} = file:consult("rebar.config"),
  514. proplists:get_value(deps, Config);
  515. {ok, [Deps]} ->
  516. [{binary_to_atom(Name, utf8), "", Source} || {Name, Source, _Level} <- Deps];
  517. _ ->
  518. {ok, Config} = file:consult("rebar.config"),
  519. proplists:get_value(deps, Config)
  520. end.
  521. format_errors(Source, Errors) ->
  522. format_errors(Source, "", Errors).
  523. format_warnings(Source, Warnings) ->
  524. format_warnings(Source, Warnings, []).
  525. format_warnings(Source, Warnings, Opts) ->
  526. Prefix = case lists:member(warnings_as_errors, Opts) of
  527. true -> "";
  528. false -> "Warning: "
  529. end,
  530. format_errors(Source, Prefix, Warnings).
  531. format_errors(_MainSource, Extra, Errors) ->
  532. [begin
  533. [format_error(Source, Extra, Desc) || Desc <- Descs]
  534. end
  535. || {Source, Descs} <- Errors].
  536. format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) ->
  537. ErrorDesc = Mod:format_error(Desc),
  538. io_lib:format("~s:~w:~w: ~s~s~n", [AbsSource, Line, Column, Extra, ErrorDesc]);
  539. format_error(AbsSource, Extra, {Line, Mod, Desc}) ->
  540. ErrorDesc = Mod:format_error(Desc),
  541. io_lib:format("~s:~w: ~s~s~n", [AbsSource, Line, Extra, ErrorDesc]);
  542. format_error(AbsSource, Extra, {Mod, Desc}) ->
  543. ErrorDesc = Mod:format_error(Desc),
  544. io_lib:format("~s: ~s~s~n", [AbsSource, Extra, ErrorDesc]).
  545. additional_defines() ->
  546. [{d, D} || {Re, D} <- [{"^[0-9]+", namespaced_types},
  547. {"^R1[4|5]", deprecated_crypto},
  548. {"^2", unicode_str},
  549. {"^(R|1|20)", fun_stacktrace},
  550. {"^((1[8|9])|2)", rand_module}],
  551. is_otp_release(Re)].
  552. is_otp_release(ArchRegex) ->
  553. case re:run(otp_release(), ArchRegex, [{capture, none}]) of
  554. match ->
  555. true;
  556. nomatch ->
  557. false
  558. end.
  559. otp_release() ->
  560. otp_release1(erlang:system_info(otp_release)).
  561. %% If OTP <= R16, otp_release is already what we want.
  562. otp_release1([$R,N|_]=Rel) when is_integer(N) ->
  563. Rel;
  564. %% If OTP >= 17.x, erlang:system_info(otp_release) returns just the
  565. %% major version number, we have to read the full version from
  566. %% a file. See http://www.erlang.org/doc/system_principles/versions.html
  567. %% Read vsn string from the 'OTP_VERSION' file and return as list without
  568. %% the "\n".
  569. otp_release1(Rel) ->
  570. File = filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]),
  571. case file:read_file(File) of
  572. {error, _} ->
  573. Rel;
  574. {ok, Vsn} ->
  575. %% It's fine to rely on the binary module here because we can
  576. %% be sure that it's available when the otp_release string does
  577. %% not begin with $R.
  578. Size = byte_size(Vsn),
  579. %% The shortest vsn string consists of at least two digits
  580. %% followed by "\n". Therefore, it's safe to assume Size >= 3.
  581. case binary:part(Vsn, {Size, -3}) of
  582. <<"**\n">> ->
  583. %% The OTP documentation mentions that a system patched
  584. %% using the otp_patch_apply tool available to licensed
  585. %% customers will leave a '**' suffix in the version as a
  586. %% flag saying the system consists of application versions
  587. %% from multiple OTP versions. We ignore this flag and
  588. %% drop the suffix, given for all intents and purposes, we
  589. %% cannot obtain relevant information from it as far as
  590. %% tooling is concerned.
  591. binary:bin_to_list(Vsn, {0, Size - 3});
  592. _ ->
  593. binary:bin_to_list(Vsn, {0, Size - 1})
  594. end
  595. end.
  596. set_proxy_auth([]) ->
  597. ok;
  598. set_proxy_auth(UserInfo) ->
  599. [Username, Password] = re:split(UserInfo, ":",
  600. [{return, list}, {parts,2}, unicode]),
  601. %% password may contain url encoded characters, need to decode them first
  602. put(proxy_auth, [{proxy_auth, {Username, http_uri:decode(Password)}}]).
  603. get_proxy_auth() ->
  604. case get(proxy_auth) of
  605. undefined -> [];
  606. ProxyAuth -> ProxyAuth
  607. end.
  608. %% string:join/2 copy; string:join/2 is getting obsoleted
  609. %% and replaced by lists:join/2, but lists:join/2 is too new
  610. %% for version support (only appeared in 19.0) so it cannot be
  611. %% used. Instead we just adopt join/2 locally and hope it works
  612. %% for most unicode use cases anyway.
  613. join([], Sep) when is_list(Sep) ->
  614. [];
  615. join([H|T], Sep) ->
  616. H ++ lists:append([Sep ++ X || X <- T]).
  617. %% Same for chr; no non-deprecated equivalent in OTP20+
  618. chr(S, C) when is_integer(C) -> chr(S, C, 1).
  619. chr([C|_Cs], C, I) -> I;
  620. chr([_|Cs], C, I) -> chr(Cs, C, I+1);
  621. chr([], _C, _I) -> 0.