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.

686 lines
25 KiB

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