Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

769 строки
28 KiB

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