Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

755 Zeilen
28 KiB

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