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.

847 lines
30 KiB

15 years ago
15 years ago
15 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  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. beam_to_mod/1,
  40. beam_to_mod/2,
  41. erl_to_mod/1,
  42. beams/1,
  43. find_executable/1,
  44. vcs_vsn/3,
  45. deprecated/3,
  46. deprecated/4,
  47. indent/1,
  48. update_code/1,
  49. update_code/2,
  50. remove_from_code_path/1,
  51. cleanup_code_path/1,
  52. args_to_tasks/1,
  53. expand_env_variable/3,
  54. get_arch/0,
  55. wordsize/0,
  56. deps_to_binary/1,
  57. tup_dedup/1,
  58. tup_umerge/2,
  59. tup_sort/1,
  60. tup_find/2,
  61. line_count/1,
  62. set_httpc_options/0,
  63. url_append_path/2,
  64. escape_chars/1,
  65. escape_double_quotes/1,
  66. escape_double_quotes_weak/1,
  67. check_min_otp_version/1,
  68. check_blacklisted_otp_versions/1,
  69. info_useless/2,
  70. list_dir/1,
  71. user_agent/0]).
  72. %% for internal use only
  73. -export([otp_release/0]).
  74. -include("rebar.hrl").
  75. -define(ONE_LEVEL_INDENT, " ").
  76. -define(APP_NAME_INDEX, 2).
  77. %% ====================================================================
  78. %% Public API
  79. %% ====================================================================
  80. sort_deps(Deps) ->
  81. %% We need a sort stable, based on the name. So that for multiple deps on
  82. %% the same level with the same name, we keep the order the parents had.
  83. %% `lists:keysort/2' is documented as stable in the stdlib.
  84. %% The list of deps is revered when we get it. For the proper stable
  85. %% result, re-reverse it.
  86. lists:keysort(?APP_NAME_INDEX, lists:reverse(Deps)).
  87. droplast(L) ->
  88. lists:reverse(tl(lists:reverse(L))).
  89. filtermap(F, [Hd|Tail]) ->
  90. case F(Hd) of
  91. true ->
  92. [Hd|filtermap(F, Tail)];
  93. {true,Val} ->
  94. [Val|filtermap(F, Tail)];
  95. false ->
  96. filtermap(F, Tail)
  97. end;
  98. filtermap(F, []) when is_function(F, 1) -> [].
  99. is_arch(ArchRegex) ->
  100. case re:run(get_arch(), ArchRegex, [{capture, none}]) of
  101. match ->
  102. true;
  103. nomatch ->
  104. false
  105. end.
  106. get_arch() ->
  107. Words = wordsize(),
  108. otp_release() ++ "-"
  109. ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
  110. wordsize() ->
  111. try erlang:system_info({wordsize, external}) of
  112. Val ->
  113. integer_to_list(8 * Val)
  114. catch
  115. error:badarg ->
  116. integer_to_list(8 * erlang:system_info(wordsize))
  117. end.
  118. sh_send(Command0, String, Options0) ->
  119. ?INFO("sh_send info:\n\tcwd: ~p\n\tcmd: ~s < ~s\n",
  120. [rebar_dir:get_cwd(), Command0, String]),
  121. ?DEBUG("\topts: ~p\n", [Options0]),
  122. DefaultOptions = [use_stdout, abort_on_error],
  123. Options = [expand_sh_flag(V)
  124. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  125. Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))),
  126. PortSettings = proplists:get_all_values(port_settings, Options) ++
  127. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
  128. Port = open_port({spawn, Command}, PortSettings),
  129. try
  130. %% allow us to send some data to the shell command's STDIN
  131. %% Erlang doesn't let us get any reply after sending an EOF, though...
  132. Port ! {self(), {command, String}}
  133. after
  134. port_close(Port)
  135. end.
  136. %%
  137. %% Options = [Option] -- defaults to [use_stdout, abort_on_error]
  138. %% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env}
  139. %% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()}
  140. %% OutputOption = use_stdout | {use_stdout, bool()}
  141. %% Env = [{string(), Val}]
  142. %% Val = string() | false
  143. %%
  144. sh(Command0, Options0) ->
  145. ?DEBUG("sh info:\n\tcwd: ~p\n\tcmd: ~s\n", [rebar_dir:get_cwd(), Command0]),
  146. ?DEBUG("\topts: ~p\n", [Options0]),
  147. DefaultOptions = [{use_stdout, false}, debug_and_abort_on_error],
  148. Options = [expand_sh_flag(V)
  149. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  150. ErrorHandler = proplists:get_value(error_handler, Options),
  151. OutputHandler = proplists:get_value(output_handler, Options),
  152. Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))),
  153. PortSettings = proplists:get_all_values(port_settings, Options) ++
  154. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof],
  155. ?DEBUG("Port Cmd: ~s\nPort Opts: ~p\n", [Command, PortSettings]),
  156. Port = open_port({spawn, Command}, PortSettings),
  157. try
  158. case sh_loop(Port, OutputHandler, []) of
  159. {ok, _Output} = Ok ->
  160. Ok;
  161. {error, {_Rc, _Output}=Err} ->
  162. ErrorHandler(Command, Err)
  163. end
  164. after
  165. port_close(Port)
  166. end.
  167. find_files(Dir, Regex) ->
  168. find_files(Dir, Regex, true).
  169. find_files(Dir, Regex, Recursive) ->
  170. filelib:fold_files(Dir, Regex, Recursive,
  171. fun(F, Acc) -> [F | Acc] end, []).
  172. find_executable(Name) ->
  173. case os:find_executable(Name) of
  174. false -> false;
  175. Path ->
  176. "\"" ++ filename:nativename(Path) ++ "\""
  177. end.
  178. deprecated(Old, New, Opts, When) when is_list(Opts) ->
  179. case lists:member(Old, Opts) of
  180. true ->
  181. deprecated(Old, New, When);
  182. false ->
  183. ok
  184. end;
  185. deprecated(Old, New, Config, When) ->
  186. case rebar_state:get(Config, Old, undefined) of
  187. undefined ->
  188. ok;
  189. _ ->
  190. deprecated(Old, New, When)
  191. end.
  192. deprecated(Old, New, When) ->
  193. io:format(
  194. <<"WARNING: deprecated ~p option used~n"
  195. "Option '~p' has been deprecated~n"
  196. "in favor of '~p'.~n"
  197. "'~p' will be removed ~s.~n">>,
  198. [Old, Old, New, Old, When]).
  199. %% for use by `do` task
  200. %% note: this does not handle the case where you have an argument that
  201. %% was enclosed in quotes and might have commas but should not be split.
  202. args_to_tasks(Args) -> new_task(Args, []).
  203. deps_to_binary([]) ->
  204. [];
  205. deps_to_binary([{Name, _, Source} | T]) ->
  206. [{ec_cnv:to_binary(Name), Source} | deps_to_binary(T)];
  207. deps_to_binary([{Name, Source} | T]) ->
  208. [{ec_cnv:to_binary(Name), Source} | deps_to_binary(T)];
  209. deps_to_binary([Name | T]) ->
  210. [ec_cnv:to_binary(Name) | deps_to_binary(T)].
  211. tup_dedup(List) ->
  212. tup_dedup_(tup_sort(List)).
  213. tup_dedup_([]) ->
  214. [];
  215. tup_dedup_([A]) ->
  216. [A];
  217. tup_dedup_([A,B|T]) when element(1, A) =:= element(1, B) ->
  218. tup_dedup_([A | T]);
  219. tup_dedup_([A,B|T]) when element(1, A) =:= B ->
  220. tup_dedup_([A | T]);
  221. tup_dedup_([A,B|T]) when A =:= element(1, B) ->
  222. tup_dedup_([A | T]);
  223. tup_dedup_([A,A|T]) ->
  224. [A|tup_dedup_(T)];
  225. tup_dedup_([A|T]) ->
  226. [A|tup_dedup_(T)].
  227. %% Sort the list in proplist-order, meaning that `{a,b}' and `{a,c}'
  228. %% both compare as usual, and `a' and `b' do the same, but `a' and `{a,b}' will
  229. %% compare based on the first element of the key, and in order. So the following
  230. %% list will sort as:
  231. %% - `[native, {native,o3}, check]' -> `[check, native, {native, o3}]'
  232. %% - `[native, {native,o3}, {native, o2}, check]' -> `[check,native,{native,o3},{native,o2}]'
  233. %% Meaning that:
  234. %% a) no deduplication takes place
  235. %% b) the key of a tuple is what counts in being sorted, but atoms are seen as {atom}
  236. %% as far as comparison is concerned (departing from lists:ukeysort/2)
  237. %% c) order is preserved for similar keys and tuples no matter their size (sort is stable)
  238. %%
  239. %% These properties let us merge proplists fairly easily.
  240. tup_sort(List) ->
  241. lists:sort(fun(A, B) when is_tuple(A), is_tuple(B) -> element(1, A) =< element(1, B)
  242. ; (A, B) when is_tuple(A) -> element(1, A) =< B
  243. ; (A, B) when is_tuple(B) -> A =< element(1, B)
  244. ; (A, B) -> A =< B
  245. end, List).
  246. %% Custom merge functions. The objective is to behave like lists:umerge/2,
  247. %% except that we also compare the merge elements based on the key if they're a
  248. %% tuple, such that `{key, val1}' is always prioritized over `{key, val0}' if
  249. %% the former is from the 'new' list.
  250. %%
  251. %% This lets us apply proper overrides to list of elements according to profile
  252. %% priority. This function depends on a stable proplist sort.
  253. tup_umerge(NewList, OldList) ->
  254. tup_umerge_(tup_sort(NewList), tup_sort(OldList)).
  255. tup_umerge_([], Olds) ->
  256. Olds;
  257. tup_umerge_([New|News], Olds) ->
  258. tup_umerge_dedup_(umerge(new, News, Olds, [], New), []).
  259. %% removes 100% identical duplicate elements so that
  260. %% `[a,{a,b},a,{a,c},a]' returns `[a,{a,b},{a,c}]'.
  261. %% Operates on a reverted list that gets reversed as part of this pass
  262. tup_umerge_dedup_([], Acc) ->
  263. Acc;
  264. tup_umerge_dedup_([H|T], Acc) ->
  265. case lists:member(H,T) of
  266. true -> tup_umerge_dedup_(T, Acc);
  267. false -> tup_umerge_dedup_(T, [H|Acc])
  268. end.
  269. tup_find(_Elem, []) ->
  270. false;
  271. tup_find(Elem, [Elem | _Elems]) ->
  272. Elem;
  273. tup_find(Elem, [Elem1 | Elems]) when is_tuple(Elem1) ->
  274. case element(1, Elem1) =:= Elem of
  275. true ->
  276. Elem1;
  277. false ->
  278. tup_find(Elem, Elems)
  279. end;
  280. tup_find(Elem, [_Elem | Elems]) ->
  281. tup_find(Elem, Elems).
  282. -spec umerge(new|old, News, Olds, Acc, Current) -> Merged when
  283. News :: [term()],
  284. Olds :: [term()],
  285. Acc :: [term()],
  286. Current :: term(),
  287. Merged :: [term()].
  288. umerge(_, [], [], Acc, Current) ->
  289. [Current | Acc];
  290. umerge(new, News, [], Acc, Current) ->
  291. %% only news left
  292. lists:reverse(News, [Current|Acc]);
  293. umerge(old, [], Olds, Acc, Current) ->
  294. %% only olds left
  295. lists:reverse(Olds, [Current|Acc]);
  296. umerge(new, News, [Old|Olds], Acc, Current) ->
  297. {Dir, Merged, NewCurrent} = compare({new, Current}, {old, Old}),
  298. umerge(Dir, News, Olds, [Merged|Acc], NewCurrent);
  299. umerge(old, [New|News], Olds, Acc, Current) ->
  300. {Dir, Merged, NewCurrent} = compare({new, New}, {old, Current}),
  301. umerge(Dir, News, Olds, [Merged|Acc], NewCurrent).
  302. -spec compare({Priority, term()}, {Secondary, term()}) ->
  303. {NextPriority, Merged, Larger} when
  304. Priority :: new | old,
  305. Secondary :: new | old,
  306. NextPriority :: new | old,
  307. Merged :: term(),
  308. Larger :: term().
  309. compare({Priority, A}, {Secondary, B}) when is_tuple(A), is_tuple(B) ->
  310. KA = element(1,A),
  311. KB = element(1,B),
  312. if KA == KB -> {Secondary, A, B};
  313. KA < KB -> {Secondary, A, B};
  314. KA > KB -> {Priority, B, A}
  315. end;
  316. compare({Priority, A}, {Secondary, B}) when not is_tuple(A), not is_tuple(B) ->
  317. if A == B -> {Secondary, A, B};
  318. A < B -> {Secondary, A, B};
  319. A > B -> {Priority, B, A}
  320. end;
  321. compare({Priority, A}, {Secondary, B}) when is_tuple(A), not is_tuple(B) ->
  322. KA = element(1,A),
  323. if KA == B -> {Secondary, A, B};
  324. KA < B -> {Secondary, A, B};
  325. KA > B -> {Priority, B, A}
  326. end;
  327. compare({Priority, A}, {Secondary, B}) when not is_tuple(A), is_tuple(B) ->
  328. KB = element(1,B),
  329. if A == KB -> {Secondary, A, B};
  330. A < KB -> {Secondary, A, B};
  331. A > KB -> {Priority, B, A}
  332. end.
  333. %% Implements wc -l functionality used to determine patchcount from git output
  334. line_count(PatchLines) ->
  335. Tokenized = string:tokens(PatchLines, "\n"),
  336. {ok, length(Tokenized)}.
  337. check_min_otp_version(undefined) ->
  338. ok;
  339. check_min_otp_version(MinOtpVersion) ->
  340. %% Fully-qualify with ?MODULE so the function can be meck'd in rebar_utils_SUITE
  341. OtpRelease = ?MODULE:otp_release(),
  342. ParsedMin = version_tuple(MinOtpVersion),
  343. ParsedVsn = version_tuple(OtpRelease),
  344. case ParsedVsn >= ParsedMin of
  345. true ->
  346. ?DEBUG("~s satisfies the requirement for minimum OTP version ~s",
  347. [OtpRelease, MinOtpVersion]);
  348. false ->
  349. ?ABORT("OTP release ~s or later is required. Version in use: ~s",
  350. [MinOtpVersion, OtpRelease])
  351. end.
  352. check_blacklisted_otp_versions(undefined) ->
  353. ok;
  354. check_blacklisted_otp_versions(BlacklistedRegexes) ->
  355. %% Fully-qualify with ?MODULE so the function can be meck'd in rebar_utils_SUITE
  356. OtpRelease = ?MODULE:otp_release(),
  357. lists:foreach(
  358. fun(BlacklistedRegex) -> abort_if_blacklisted(BlacklistedRegex, OtpRelease) end,
  359. BlacklistedRegexes).
  360. abort_if_blacklisted(BlacklistedRegex, OtpRelease) ->
  361. case re:run(OtpRelease, BlacklistedRegex, [{capture, none}]) of
  362. match ->
  363. ?ABORT("OTP release ~s matches blacklisted version ~s",
  364. [OtpRelease, BlacklistedRegex]);
  365. nomatch ->
  366. ?DEBUG("~s does not match blacklisted OTP version ~s",
  367. [OtpRelease, BlacklistedRegex])
  368. end.
  369. user_agent() ->
  370. {ok, Vsn} = application:get_key(rebar, vsn),
  371. ?FMT("Rebar/~s (OTP/~s)", [Vsn, otp_release()]).
  372. %% ====================================================================
  373. %% Internal functions
  374. %% ====================================================================
  375. version_tuple(OtpRelease) ->
  376. case re:run(OtpRelease, "R?(\\d+)B?.?-?(\\d+)?.?-?(\\d+)?", [{capture, all, list}]) of
  377. {match, [_Full, Maj, Min, Patch]} ->
  378. {list_to_integer(Maj), list_to_integer(Min), list_to_integer(Patch)};
  379. {match, [_Full, Maj, Min]} ->
  380. {list_to_integer(Maj), list_to_integer(Min), 0};
  381. {match, [_Full, Maj]} ->
  382. {list_to_integer(Maj), 0, 0};
  383. nomatch ->
  384. ?ABORT("Minimum OTP release unable to be parsed: ~s", [OtpRelease])
  385. end.
  386. otp_release() ->
  387. otp_release1(erlang:system_info(otp_release)).
  388. %% If OTP <= R16, otp_release is already what we want.
  389. otp_release1([$R,N|_]=Rel) when is_integer(N) ->
  390. Rel;
  391. %% If OTP >= 17.x, erlang:system_info(otp_release) returns just the
  392. %% major version number, we have to read the full version from
  393. %% a file. See http://www.erlang.org/doc/system_principles/versions.html
  394. %% Read vsn string from the 'OTP_VERSION' file and return as list without
  395. %% the "\n".
  396. otp_release1(Rel) ->
  397. File = filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]),
  398. case file:read_file(File) of
  399. {error, _} ->
  400. Rel;
  401. {ok, Vsn} ->
  402. %% It's fine to rely on the binary module here because we can
  403. %% be sure that it's available when the otp_release string does
  404. %% not begin with $R.
  405. Size = byte_size(Vsn),
  406. %% The shortest vsn string consists of at least two digits
  407. %% followed by "\n". Therefore, it's safe to assume Size >= 3.
  408. case binary:part(Vsn, {Size, -3}) of
  409. <<"**\n">> ->
  410. %% The OTP documentation mentions that a system patched
  411. %% using the otp_patch_apply tool available to licensed
  412. %% customers will leave a '**' suffix in the version as a
  413. %% flag saying the system consists of application versions
  414. %% from multiple OTP versions. We ignore this flag and
  415. %% drop the suffix, given for all intents and purposes, we
  416. %% cannot obtain relevant information from it as far as
  417. %% tooling is concerned.
  418. binary:bin_to_list(Vsn, {0, Size - 3});
  419. _ ->
  420. binary:bin_to_list(Vsn, {0, Size - 1})
  421. end
  422. end.
  423. %% We do the shell variable substitution ourselves on Windows and hope that the
  424. %% command doesn't use any other shell magic.
  425. patch_on_windows(Cmd, Env) ->
  426. case os:type() of
  427. {win32,nt} ->
  428. Cmd1 = "cmd /q /c "
  429. ++ lists:foldl(fun({Key, Value}, Acc) ->
  430. expand_env_variable(Acc, Key, Value)
  431. end, Cmd, Env),
  432. %% Remove left-over vars
  433. re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
  434. [global, {return, list}]);
  435. _ ->
  436. Cmd
  437. end.
  438. %%
  439. %% Given env. variable FOO we want to expand all references to
  440. %% it in InStr. References can have two forms: $FOO and ${FOO}
  441. %% The end of form $FOO is delimited with whitespace or eol
  442. %%
  443. expand_env_variable(InStr, VarName, RawVarValue) ->
  444. case string:chr(InStr, $$) of
  445. 0 ->
  446. %% No variables to expand
  447. InStr;
  448. _ ->
  449. ReOpts = [global, unicode, {return, list}],
  450. VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
  451. %% Use a regex to match/replace:
  452. %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
  453. RegEx = io_lib:format("\\\$(~s(\\W|$)|{~s})", [VarName, VarName]),
  454. re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
  455. end.
  456. expand_sh_flag(return_on_error) ->
  457. {error_handler,
  458. fun(_Command, Err) ->
  459. {error, Err}
  460. end};
  461. expand_sh_flag(abort_on_error) ->
  462. {error_handler,
  463. fun log_and_abort/2};
  464. expand_sh_flag({abort_on_error, Message}) ->
  465. {error_handler,
  466. log_msg_and_abort(Message)};
  467. expand_sh_flag({debug_abort_on_error, Message}) ->
  468. {error_handler,
  469. debug_log_msg_and_abort(Message)};
  470. expand_sh_flag(debug_and_abort_on_error) ->
  471. {error_handler,
  472. fun debug_and_abort/2};
  473. expand_sh_flag(use_stdout) ->
  474. {output_handler,
  475. fun(Line, Acc) ->
  476. %% Line already has a newline so don't use ?CONSOLE which adds one
  477. io:format("~s", [Line]),
  478. [Line | Acc]
  479. end};
  480. expand_sh_flag({use_stdout, false}) ->
  481. {output_handler,
  482. fun(Line, Acc) ->
  483. [Line | Acc]
  484. end};
  485. expand_sh_flag({cd, _CdArg} = Cd) ->
  486. {port_settings, Cd};
  487. expand_sh_flag({env, _EnvArg} = Env) ->
  488. {port_settings, Env}.
  489. -type err_handler() :: fun((string(), {integer(), string()}) -> no_return()).
  490. -spec log_msg_and_abort(string()) -> err_handler().
  491. log_msg_and_abort(Message) ->
  492. fun(_Command, {_Rc, _Output}) ->
  493. ?ABORT(Message, [])
  494. end.
  495. -spec debug_log_msg_and_abort(string()) -> err_handler().
  496. debug_log_msg_and_abort(Message) ->
  497. fun(Command, {Rc, Output}) ->
  498. ?DEBUG("sh(~s)~n"
  499. "failed with return code ~w and the following output:~n"
  500. "~s", [Command, Rc, Output]),
  501. ?ABORT(Message, [])
  502. end.
  503. -spec log_and_abort(string(), {integer(), string()}) -> no_return().
  504. log_and_abort(Command, {Rc, Output}) ->
  505. ?ABORT("sh(~s)~n"
  506. "failed with return code ~w and the following output:~n"
  507. "~s", [Command, Rc, Output]).
  508. -spec debug_and_abort(string(), {integer(), string()}) -> no_return().
  509. debug_and_abort(Command, {Rc, Output}) ->
  510. ?DEBUG("sh(~s)~n"
  511. "failed with return code ~w and the following output:~n"
  512. "~s", [Command, Rc, Output]),
  513. throw(rebar_abort).
  514. sh_loop(Port, Fun, Acc) ->
  515. receive
  516. {Port, {data, {eol, Line}}} ->
  517. sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
  518. {Port, {data, {noeol, Line}}} ->
  519. sh_loop(Port, Fun, Fun(Line, Acc));
  520. {Port, eof} ->
  521. Data = lists:flatten(lists:reverse(Acc)),
  522. receive
  523. {Port, {exit_status, 0}} ->
  524. {ok, Data};
  525. {Port, {exit_status, Rc}} ->
  526. {error, {Rc, Data}}
  527. end
  528. end.
  529. beam_to_mod(Dir, Filename) ->
  530. [Dir | Rest] = filename:split(Filename),
  531. list_to_atom(filename:basename(string:join(Rest, "."), ".beam")).
  532. beam_to_mod(Filename) ->
  533. list_to_atom(filename:basename(Filename, ".beam")).
  534. erl_to_mod(Filename) ->
  535. list_to_atom(filename:rootname(filename:basename(Filename))).
  536. beams(Dir) ->
  537. filelib:wildcard(filename:join(Dir, "*.beam")).
  538. -spec abort() -> no_return().
  539. abort() ->
  540. throw(rebar_abort).
  541. -spec abort(string(), [term()]) -> no_return().
  542. abort(String, Args) ->
  543. ?ERROR(String, Args),
  544. abort().
  545. escript_foldl(Fun, Acc, File) ->
  546. case escript:extract(File, [compile_source]) of
  547. {ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
  548. case Body of
  549. {source, BeamCode} ->
  550. GetInfo = fun() -> file:read_file_info(File) end,
  551. GetBin = fun() -> BeamCode end,
  552. {ok, Fun(".", GetInfo, GetBin, Acc)};
  553. {beam, BeamCode} ->
  554. GetInfo = fun() -> file:read_file_info(File) end,
  555. GetBin = fun() -> BeamCode end,
  556. {ok, Fun(".", GetInfo, GetBin, Acc)};
  557. {archive, ArchiveBin} ->
  558. zip:foldl(Fun, Acc, {File, ArchiveBin})
  559. end;
  560. {error, _} = Error ->
  561. Error
  562. end.
  563. vcs_vsn(Vcs, Dir, Resources) ->
  564. case vcs_vsn_cmd(Vcs, Dir, Resources) of
  565. {plain, VsnString} ->
  566. VsnString;
  567. {cmd, CmdString} ->
  568. vcs_vsn_invoke(CmdString, Dir);
  569. unknown ->
  570. ?ABORT("vcs_vsn: Unknown vsn format: ~p", [Vcs]);
  571. {error, Reason} ->
  572. ?ABORT("vcs_vsn: ~s", [Reason])
  573. end.
  574. %% Temp work around for repos like relx that use "semver"
  575. vcs_vsn_cmd(Vsn, _, _) when is_binary(Vsn) ->
  576. {plain, Vsn};
  577. vcs_vsn_cmd(VCS, Dir, Resources) when VCS =:= semver ; VCS =:= "semver" ->
  578. vcs_vsn_cmd(git, Dir, Resources);
  579. vcs_vsn_cmd({cmd, _Cmd}=Custom, _, _) ->
  580. Custom;
  581. vcs_vsn_cmd(VCS, Dir, Resources) when is_atom(VCS) ->
  582. case find_resource_module(VCS, Resources) of
  583. {ok, Module} ->
  584. Module:make_vsn(Dir);
  585. {error, _} ->
  586. unknown
  587. end;
  588. vcs_vsn_cmd(VCS, Dir, Resources) when is_list(VCS) ->
  589. try list_to_existing_atom(VCS) of
  590. AVCS ->
  591. case vcs_vsn_cmd(AVCS, Dir, Resources) of
  592. unknown -> {plain, VCS};
  593. Other -> Other
  594. end
  595. catch
  596. error:badarg ->
  597. {plain, VCS}
  598. end;
  599. vcs_vsn_cmd(_, _, _) ->
  600. unknown.
  601. vcs_vsn_invoke(Cmd, Dir) ->
  602. {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
  603. string:strip(VsnString, right, $\n).
  604. find_resource_module(Type, Resources) ->
  605. case lists:keyfind(Type, 1, Resources) of
  606. false ->
  607. case code:which(Type) of
  608. non_existing ->
  609. {error, unknown};
  610. _ ->
  611. {ok, Type}
  612. end;
  613. {Type, Module} ->
  614. {ok, Module}
  615. end.
  616. %% @doc ident to the level specified
  617. -spec indent(non_neg_integer()) -> iolist().
  618. indent(Amount) when erlang:is_integer(Amount) ->
  619. [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
  620. %% Replace code paths with new paths for existing apps and
  621. %% purge code of the old modules from those apps.
  622. update_code(Paths) -> update_code(Paths, []).
  623. update_code(Paths, Opts) ->
  624. lists:foreach(fun(Path) ->
  625. Name = filename:basename(Path, "/ebin"),
  626. App = list_to_atom(Name),
  627. application:load(App),
  628. case application:get_key(App, modules) of
  629. undefined ->
  630. code:add_patha(Path),
  631. ok;
  632. {ok, Modules} ->
  633. %% replace_path causes problems when running
  634. %% tests in projects like erlware_commons that rebar3
  635. %% also includes
  636. %code:replace_path(App, Path),
  637. code:del_path(App),
  638. code:add_patha(Path),
  639. case lists:member(soft_purge, Opts) of
  640. true ->
  641. [begin code:soft_purge(M), code:delete(M) end || M <- Modules];
  642. false ->
  643. [begin code:purge(M), code:delete(M) end || M <- Modules]
  644. end
  645. end
  646. end, Paths).
  647. remove_from_code_path(Paths) ->
  648. lists:foreach(fun(Path) ->
  649. Name = filename:basename(Path, "/ebin"),
  650. App = list_to_atom(Name),
  651. application:load(App),
  652. case application:get_key(App, modules) of
  653. undefined ->
  654. application:unload(App),
  655. ok;
  656. {ok, Modules} ->
  657. application:unload(App),
  658. [begin code:purge(M), code:delete(M) end || M <- Modules]
  659. end,
  660. code:del_path(Path)
  661. end, Paths).
  662. cleanup_code_path(OrigPath) ->
  663. CurrentPath = code:get_path(),
  664. AddedPaths = CurrentPath -- OrigPath,
  665. %% If someone has removed paths, it's hard to get them back into
  666. %% the right order, but since this is currently rare, we can just
  667. %% fall back to code:set_path/1.
  668. case CurrentPath -- AddedPaths of
  669. OrigPath ->
  670. _ = [code:del_path(Path) || Path <- AddedPaths],
  671. true;
  672. _ ->
  673. code:set_path(OrigPath)
  674. end.
  675. new_task([], Acc) -> lists:reverse(Acc);
  676. new_task([TaskList|Rest], Acc) ->
  677. case re:split(TaskList, ",", [{return, list}, {parts, 2}]) of
  678. %% `do` consumes all remaining args
  679. ["do" = Task] ->
  680. lists:reverse([{Task, Rest}|Acc]);
  681. %% single task terminated by a comma
  682. [Task, ""] -> new_task(Rest, [{Task, []}|Acc]);
  683. %% sequence of two or more tasks
  684. [Task, More] -> new_task([More|Rest], [{Task, []}|Acc]);
  685. %% single task not terminated by a comma
  686. [Task] -> arg_or_flag(Rest, [{Task, []}|Acc])
  687. end.
  688. arg_or_flag([], [{Task, Args}|Acc]) ->
  689. lists:reverse([{Task, lists:reverse(Args)}|Acc]);
  690. %% case where you have `foo , bar`
  691. arg_or_flag([","|Rest], Acc) -> new_task(Rest, Acc);
  692. %% case where you have `foo ,bar`
  693. arg_or_flag(["," ++ Task|Rest], Acc) -> new_task([Task|Rest], Acc);
  694. %% a flag
  695. arg_or_flag(["-" ++ _ = Flag|Rest], [{Task, Args}|Acc]) ->
  696. case maybe_ends_in_comma(Flag) of
  697. false -> arg_or_flag(Rest, [{Task, [Flag|Args]}|Acc]);
  698. NewFlag -> new_task(Rest, [{Task,
  699. lists:reverse([NewFlag|Args])}|Acc])
  700. end;
  701. %% an argument or a sequence of arguments
  702. arg_or_flag([ArgList|Rest], [{Task, Args}|Acc]) ->
  703. case re:split(ArgList, ",", [{return, list}, {parts, 2}]) of
  704. %% single arg terminated by a comma
  705. [Arg, ""] -> new_task(Rest, [{Task,
  706. lists:reverse([Arg|Args])}|Acc]);
  707. %% sequence of two or more args/tasks
  708. [Arg, More] -> new_task([More|Rest], [{Task,
  709. lists:reverse([Arg|Args])}|Acc]);
  710. %% single arg not terminated by a comma
  711. [Arg] -> arg_or_flag(Rest, [{Task, [Arg|Args]}|Acc])
  712. end.
  713. maybe_ends_in_comma(H) ->
  714. case lists:reverse(H) of
  715. "," ++ Flag -> lists:reverse(Flag);
  716. _ -> false
  717. end.
  718. get_http_vars(Scheme) ->
  719. OS = case os:getenv(atom_to_list(Scheme)) of
  720. Str when is_list(Str) -> Str;
  721. _ -> []
  722. end,
  723. GlobalConfigFile = rebar_dir:global_config(),
  724. Config = rebar_config:consult_file(GlobalConfigFile),
  725. proplists:get_value(Scheme, Config, OS).
  726. set_httpc_options() ->
  727. set_httpc_options(https_proxy, get_http_vars(https_proxy)),
  728. set_httpc_options(proxy, get_http_vars(http_proxy)).
  729. set_httpc_options(_, []) ->
  730. ok;
  731. set_httpc_options(Scheme, Proxy) ->
  732. {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Proxy),
  733. httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar).
  734. url_append_path(Url, ExtraPath) ->
  735. case http_uri:parse(Url) of
  736. {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
  737. {ok, lists:append([atom_to_list(Scheme), "://", UserInfo, Host, ":", integer_to_list(Port),
  738. filename:join(Path, ExtraPath), "?", Query])};
  739. _ ->
  740. error
  741. end.
  742. %% escape\ as\ a\ shell\?
  743. escape_chars(Str) when is_atom(Str) ->
  744. escape_chars(atom_to_list(Str));
  745. escape_chars(Str) ->
  746. re:replace(Str, "([ ()?`!$&;])", "\\\\&", [global, {return, list}]).
  747. %% "escape inside these"
  748. escape_double_quotes(Str) ->
  749. re:replace(Str, "([\"\\\\`!$&*;])", "\\\\&", [global, {return, list}]).
  750. %% "escape inside these" but allow *
  751. escape_double_quotes_weak(Str) ->
  752. re:replace(Str, "([\"\\\\`!$&;])", "\\\\&", [global, {return, list}]).
  753. info_useless(Old, New) ->
  754. [?INFO("App ~ts is no longer needed and can be deleted.", [Name])
  755. || Name <- Old,
  756. not lists:member(Name, New)],
  757. ok.
  758. list_dir(Dir) ->
  759. %% `list_dir_all` returns raw files which are unsupported
  760. %% prior to R16 so just fall back to `list_dir` if running
  761. %% on an earlier vm
  762. case erlang:function_exported(file, list_dir_all, 1) of
  763. true -> file:list_dir_all(Dir);
  764. false -> file:list_dir(Dir)
  765. end.