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.

659 lines
24 KiB

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