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.

530 lines
18 KiB

преди 14 години
преди 15 години
преди 15 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 13 години
преди 10 години
преди 15 години
преди 10 години
преди 10 години
преди 12 години
преди 14 години
преди 14 години
преди 13 години
преди 14 години
преди 14 години
преди 12 години
преди 13 години
преди 13 години
преди 13 години
преди 13 години
преди 13 години
преди 10 години
преди 13 години
преди 10 години
преди 12 години
преди 12 години
преди 10 години
преди 10 години
преди 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. expand_code_path/0,
  45. vcs_vsn/2,
  46. deprecated/3,
  47. deprecated/4,
  48. erl_opts/1,
  49. indent/1,
  50. cleanup_code_path/1,
  51. args_to_tasks/1]).
  52. %% for internal use only
  53. -export([otp_release/0]).
  54. -include("rebar.hrl").
  55. -define(ONE_LEVEL_INDENT, " ").
  56. -define(APP_NAME_INDEX, 2).
  57. %% ====================================================================
  58. %% Public API
  59. %% ====================================================================
  60. sort_deps(Deps) ->
  61. %% We need a sort stable, based on the name. So that for multiple deps on
  62. %% the same level with the same name, we keep the order the parents had.
  63. %% `lists:keysort/2' is documented as stable in the stdlib.
  64. %% The list of deps is revered when we get it. For the proper stable
  65. %% result, re-reverse it.
  66. lists:keysort(?APP_NAME_INDEX, lists:reverse(Deps)).
  67. droplast(L) ->
  68. lists:reverse(tl(lists:reverse(L))).
  69. filtermap(F, [Hd|Tail]) ->
  70. case F(Hd) of
  71. true ->
  72. [Hd|filtermap(F, Tail)];
  73. {true,Val} ->
  74. [Val|filtermap(F, Tail)];
  75. false ->
  76. filtermap(F, Tail)
  77. end;
  78. filtermap(F, []) when is_function(F, 1) -> [].
  79. is_arch(ArchRegex) ->
  80. case re:run(get_arch(), ArchRegex, [{capture, none}]) of
  81. match ->
  82. true;
  83. nomatch ->
  84. false
  85. end.
  86. get_arch() ->
  87. Words = wordsize(),
  88. otp_release() ++ "-"
  89. ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
  90. wordsize() ->
  91. try erlang:system_info({wordsize, external}) of
  92. Val ->
  93. integer_to_list(8 * Val)
  94. catch
  95. error:badarg ->
  96. integer_to_list(8 * erlang:system_info(wordsize))
  97. end.
  98. sh_send(Command0, String, Options0) ->
  99. ?INFO("sh_send info:\n\tcwd: ~p\n\tcmd: ~s < ~s\n",
  100. [rebar_dir:get_cwd(), Command0, String]),
  101. ?DEBUG("\topts: ~p\n", [Options0]),
  102. DefaultOptions = [use_stdout, abort_on_error],
  103. Options = [expand_sh_flag(V)
  104. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  105. Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])),
  106. PortSettings = proplists:get_all_values(port_settings, Options) ++
  107. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
  108. Port = open_port({spawn, Command}, PortSettings),
  109. %% allow us to send some data to the shell command's STDIN
  110. %% Erlang doesn't let us get any reply after sending an EOF, though...
  111. Port ! {self(), {command, String}},
  112. port_close(Port).
  113. %%
  114. %% Options = [Option] -- defaults to [use_stdout, abort_on_error]
  115. %% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env}
  116. %% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()}
  117. %% OutputOption = use_stdout | {use_stdout, bool()}
  118. %% Env = [{string(), Val}]
  119. %% Val = string() | false
  120. %%
  121. sh(Command0, Options0) ->
  122. ?DEBUG("sh info:\n\tcwd: ~p\n\tcmd: ~s\n", [rebar_dir:get_cwd(), Command0]),
  123. ?DEBUG("\topts: ~p\n", [Options0]),
  124. DefaultOptions = [{use_stdout, false}, debug_and_abort_on_error],
  125. Options = [expand_sh_flag(V)
  126. || V <- proplists:compact(Options0 ++ DefaultOptions)],
  127. ErrorHandler = proplists:get_value(error_handler, Options),
  128. OutputHandler = proplists:get_value(output_handler, Options),
  129. Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])),
  130. PortSettings = proplists:get_all_values(port_settings, Options) ++
  131. [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
  132. ?DEBUG("Port Cmd: ~p\nPort Opts: ~p\n", [Command, PortSettings]),
  133. Port = open_port({spawn, Command}, PortSettings),
  134. case sh_loop(Port, OutputHandler, []) of
  135. {ok, _Output} = Ok ->
  136. Ok;
  137. {error, {_Rc, _Output}=Err} ->
  138. ErrorHandler(Command, Err)
  139. end.
  140. find_files(Dir, Regex) ->
  141. find_files(Dir, Regex, true).
  142. find_files(Dir, Regex, Recursive) ->
  143. filelib:fold_files(Dir, Regex, Recursive,
  144. fun(F, Acc) -> [F | Acc] end, []).
  145. find_executable(Name) ->
  146. case os:find_executable(Name) of
  147. false -> false;
  148. Path ->
  149. "\"" ++ filename:nativename(Path) ++ "\""
  150. end.
  151. %% Convert all the entries in the code path to absolute paths.
  152. expand_code_path() ->
  153. CodePath = lists:foldl(fun(Path, Acc) ->
  154. [filename:absname(Path) | Acc]
  155. end, [], code:get_path()),
  156. code:set_path(lists:reverse(CodePath)).
  157. deprecated(Old, New, Opts, When) when is_list(Opts) ->
  158. case lists:member(Old, Opts) of
  159. true ->
  160. deprecated(Old, New, When);
  161. false ->
  162. ok
  163. end;
  164. deprecated(Old, New, Config, When) ->
  165. case rebar_state:get(Config, Old, undefined) of
  166. undefined ->
  167. ok;
  168. _ ->
  169. deprecated(Old, New, When)
  170. end.
  171. deprecated(Old, New, When) ->
  172. io:format(
  173. <<"WARNING: deprecated ~p option used~n"
  174. "Option '~p' has been deprecated~n"
  175. "in favor of '~p'.~n"
  176. "'~p' will be removed ~s.~n">>,
  177. [Old, Old, New, Old, When]).
  178. %% @doc Return list of erl_opts
  179. -spec erl_opts(rebar_state:t()) -> list().
  180. erl_opts(Config) ->
  181. RawErlOpts = filter_defines(rebar_state:get(Config, erl_opts, []), []),
  182. Defines = [{d, list_to_atom(D)} ||
  183. D <- rebar_state:get(Config, defines, [])],
  184. Opts = Defines ++ RawErlOpts,
  185. case proplists:is_defined(no_debug_info, Opts) of
  186. true ->
  187. [O || O <- Opts, O =/= no_debug_info];
  188. false ->
  189. [debug_info|Opts]
  190. end.
  191. %% for use by `do` task
  192. %% note: this does not handle the case where you have an argument that
  193. %% was enclosed in quotes and might have commas but should not be split.
  194. args_to_tasks(Args) -> new_task(Args, []).
  195. %% ====================================================================
  196. %% Internal functions
  197. %% ====================================================================
  198. otp_release() ->
  199. otp_release1(erlang:system_info(otp_release)).
  200. %% If OTP <= R16, otp_release is already what we want.
  201. otp_release1([$R,N|_]=Rel) when is_integer(N) ->
  202. Rel;
  203. %% If OTP >= 17.x, erlang:system_info(otp_release) returns just the
  204. %% major version number, we have to read the full version from
  205. %% a file. See http://www.erlang.org/doc/system_principles/versions.html
  206. %% Read vsn string from the 'OTP_VERSION' file and return as list without
  207. %% the "\n".
  208. otp_release1(Rel) ->
  209. File = filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]),
  210. {ok, Vsn} = file:read_file(File),
  211. %% It's fine to rely on the binary module here because we can
  212. %% be sure that it's available when the otp_release string does
  213. %% not begin with $R.
  214. Size = byte_size(Vsn),
  215. %% The shortest vsn string consists of at least two digits
  216. %% followed by "\n". Therefore, it's safe to assume Size >= 3.
  217. case binary:part(Vsn, {Size, -3}) of
  218. <<"**\n">> ->
  219. %% The OTP documentation mentions that a system patched
  220. %% using the otp_patch_apply tool available to licensed
  221. %% customers will leave a '**' suffix in the version as a
  222. %% flag saying the system consists of application versions
  223. %% from multiple OTP versions. We ignore this flag and
  224. %% drop the suffix, given for all intents and purposes, we
  225. %% cannot obtain relevant information from it as far as
  226. %% tooling is concerned.
  227. binary:bin_to_list(Vsn, {0, Size - 3});
  228. _ ->
  229. binary:bin_to_list(Vsn, {0, Size - 1})
  230. end.
  231. %% We do the shell variable substitution ourselves on Windows and hope that the
  232. %% command doesn't use any other shell magic.
  233. patch_on_windows(Cmd, Env) ->
  234. case os:type() of
  235. {win32,nt} ->
  236. Cmd1 = "cmd /q /c "
  237. ++ lists:foldl(fun({Key, Value}, Acc) ->
  238. expand_env_variable(Acc, Key, Value)
  239. end, Cmd, Env),
  240. %% Remove left-over vars
  241. re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
  242. [global, {return, list}]);
  243. _ ->
  244. Cmd
  245. end.
  246. %%
  247. %% Given env. variable FOO we want to expand all references to
  248. %% it in InStr. References can have two forms: $FOO and ${FOO}
  249. %% The end of form $FOO is delimited with whitespace or eol
  250. %%
  251. expand_env_variable(InStr, VarName, RawVarValue) ->
  252. case string:chr(InStr, $$) of
  253. 0 ->
  254. %% No variables to expand
  255. InStr;
  256. _ ->
  257. ReOpts = [global, unicode, {return, list}],
  258. VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
  259. %% Use a regex to match/replace:
  260. %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
  261. RegEx = io_lib:format("\\\$(~s(\\W|$)|{~s})", [VarName, VarName]),
  262. re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
  263. end.
  264. expand_sh_flag(return_on_error) ->
  265. {error_handler,
  266. fun(_Command, Err) ->
  267. {error, Err}
  268. end};
  269. expand_sh_flag(abort_on_error) ->
  270. {error_handler,
  271. fun log_and_abort/2};
  272. expand_sh_flag({abort_on_error, Message}) ->
  273. {error_handler,
  274. log_msg_and_abort(Message)};
  275. expand_sh_flag({debug_abort_on_error, Message}) ->
  276. {error_handler,
  277. debug_log_msg_and_abort(Message)};
  278. expand_sh_flag(debug_and_abort_on_error) ->
  279. {error_handler,
  280. fun debug_and_abort/2};
  281. expand_sh_flag(use_stdout) ->
  282. {output_handler,
  283. fun(Line, Acc) ->
  284. %% Line already has a newline so don't use ?CONSOLE which adds one
  285. io:format("~s", [Line]),
  286. [Line | Acc]
  287. end};
  288. expand_sh_flag({use_stdout, false}) ->
  289. {output_handler,
  290. fun(Line, Acc) ->
  291. [Line | Acc]
  292. end};
  293. expand_sh_flag({cd, _CdArg} = Cd) ->
  294. {port_settings, Cd};
  295. expand_sh_flag({env, _EnvArg} = Env) ->
  296. {port_settings, Env}.
  297. -type err_handler() :: fun((string(), {integer(), string()}) -> no_return()).
  298. -spec log_msg_and_abort(string()) -> err_handler().
  299. log_msg_and_abort(Message) ->
  300. fun(_Command, {_Rc, _Output}) ->
  301. ?ABORT(Message, [])
  302. end.
  303. debug_log_msg_and_abort(Message) ->
  304. fun(Command, {Rc, Output}) ->
  305. ?DEBUG("sh(~s)~n"
  306. "failed with return code ~w and the following output:~n"
  307. "~s", [Command, Rc, Output]),
  308. ?ABORT(Message, [])
  309. end.
  310. -spec log_and_abort(string(), {integer(), string()}) -> no_return().
  311. log_and_abort(Command, {Rc, Output}) ->
  312. ?ABORT("sh(~s)~n"
  313. "failed with return code ~w and the following output:~n"
  314. "~s", [Command, Rc, Output]).
  315. -spec debug_and_abort(string(), {integer(), string()}) -> no_return().
  316. debug_and_abort(Command, {Rc, Output}) ->
  317. ?DEBUG("sh(~s)~n"
  318. "failed with return code ~w and the following output:~n"
  319. "~s", [Command, Rc, Output]),
  320. throw(rebar_abort).
  321. sh_loop(Port, Fun, Acc) ->
  322. receive
  323. {Port, {data, {eol, Line}}} ->
  324. sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
  325. {Port, {data, {noeol, Line}}} ->
  326. sh_loop(Port, Fun, Fun(Line, Acc));
  327. {Port, {exit_status, 0}} ->
  328. {ok, lists:flatten(lists:reverse(Acc))};
  329. {Port, {exit_status, Rc}} ->
  330. {error, {Rc, lists:flatten(lists:reverse(Acc))}}
  331. end.
  332. beam_to_mod(Dir, Filename) ->
  333. [Dir | Rest] = filename:split(Filename),
  334. list_to_atom(filename:basename(string:join(Rest, "."), ".beam")).
  335. beam_to_mod(Filename) ->
  336. list_to_atom(filename:basename(Filename, ".beam")).
  337. erl_to_mod(Filename) ->
  338. list_to_atom(filename:rootname(filename:basename(Filename))).
  339. beams(Dir) ->
  340. filelib:wildcard(filename:join(Dir, "*.beam")).
  341. -spec abort() -> no_return().
  342. abort() ->
  343. throw(rebar_abort).
  344. -spec abort(string(), [term()]) -> no_return().
  345. abort(String, Args) ->
  346. ?ERROR(String, Args),
  347. abort().
  348. escript_foldl(Fun, Acc, File) ->
  349. case escript:extract(File, [compile_source]) of
  350. {ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
  351. case Body of
  352. {source, BeamCode} ->
  353. GetInfo = fun() -> file:read_file_info(File) end,
  354. GetBin = fun() -> BeamCode end,
  355. {ok, Fun(".", GetInfo, GetBin, Acc)};
  356. {beam, BeamCode} ->
  357. GetInfo = fun() -> file:read_file_info(File) end,
  358. GetBin = fun() -> BeamCode end,
  359. {ok, Fun(".", GetInfo, GetBin, Acc)};
  360. {archive, ArchiveBin} ->
  361. zip:foldl(Fun, Acc, {File, ArchiveBin})
  362. end;
  363. {error, _} = Error ->
  364. Error
  365. end.
  366. vcs_vsn(Vcs, Dir) ->
  367. case vcs_vsn_cmd(Vcs, Dir) of
  368. {plain, VsnString} ->
  369. VsnString;
  370. {cmd, CmdString} ->
  371. vcs_vsn_invoke(CmdString, Dir);
  372. unknown ->
  373. ?ABORT("vcs_vsn: Unknown vsn format: ~p", [Vcs]);
  374. {error, Reason} ->
  375. ?ABORT("vcs_vsn: ~s", [Reason])
  376. end.
  377. %% Temp work around for repos like relx that use "semver"
  378. vcs_vsn_cmd(VCS, Dir) when VCS =:= semver ; VCS =:= "semver" ->
  379. rebar_git_resource:make_vsn(Dir);
  380. vcs_vsn_cmd(VCS, Dir) when VCS =:= git ; VCS =:= "git" ->
  381. rebar_git_resource:make_vsn(Dir);
  382. vcs_vsn_cmd(VCS, Dir) when VCS =:= pkg ; VCS =:= "pkg" ->
  383. rebar_pkg_resource:make_vsn(Dir);
  384. vcs_vsn_cmd({cmd, _Cmd}=Custom, _) ->
  385. Custom;
  386. vcs_vsn_cmd(Version, _) when is_list(Version) ->
  387. {plain, Version};
  388. vcs_vsn_cmd(_, _) ->
  389. unknown.
  390. vcs_vsn_invoke(Cmd, Dir) ->
  391. {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
  392. string:strip(VsnString, right, $\n).
  393. %%
  394. %% Filter a list of erl_opts platform_define options such that only
  395. %% those which match the provided architecture regex are returned.
  396. %%
  397. filter_defines([], Acc) ->
  398. lists:reverse(Acc);
  399. filter_defines([{platform_define, ArchRegex, Key} | Rest], Acc) ->
  400. case rebar_utils:is_arch(ArchRegex) of
  401. true ->
  402. filter_defines(Rest, [{d, Key} | Acc]);
  403. false ->
  404. filter_defines(Rest, Acc)
  405. end;
  406. filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) ->
  407. case rebar_utils:is_arch(ArchRegex) of
  408. true ->
  409. filter_defines(Rest, [{d, Key, Value} | Acc]);
  410. false ->
  411. filter_defines(Rest, Acc)
  412. end;
  413. filter_defines([Opt | Rest], Acc) ->
  414. filter_defines(Rest, [Opt | Acc]).
  415. %% @doc ident to the level specified
  416. -spec indent(non_neg_integer()) -> iolist().
  417. indent(Amount) when erlang:is_integer(Amount) ->
  418. [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
  419. cleanup_code_path(OrigPath) ->
  420. CurrentPath = code:get_path(),
  421. AddedPaths = CurrentPath -- OrigPath,
  422. %% If someone has removed paths, it's hard to get them back into
  423. %% the right order, but since this is currently rare, we can just
  424. %% fall back to code:set_path/1.
  425. case CurrentPath -- AddedPaths of
  426. OrigPath ->
  427. _ = [code:del_path(Path) || Path <- AddedPaths],
  428. true;
  429. _ ->
  430. code:set_path(OrigPath)
  431. end.
  432. new_task([], Acc) -> lists:reverse(Acc);
  433. new_task([TaskList|Rest], Acc) ->
  434. case re:split(TaskList, ",", [{return, list}, {parts, 2}]) of
  435. %% `do` consumes all remaining args
  436. ["do" = Task] ->
  437. lists:reverse([{Task, Rest}|Acc]);
  438. %% single task terminated by a comma
  439. [Task, ""] -> new_task(Rest, [{Task, []}|Acc]);
  440. %% sequence of two or more tasks
  441. [Task, More] -> new_task([More|Rest], [{Task, []}|Acc]);
  442. %% single task not terminated by a comma
  443. [Task] -> arg_or_flag(Rest, [{Task, []}|Acc])
  444. end.
  445. arg_or_flag([], [{Task, Args}|Acc]) ->
  446. lists:reverse([{Task, lists:reverse(Args)}|Acc]);
  447. %% case where you have `foo , bar`
  448. arg_or_flag([","|Rest], Acc) -> new_task(Rest, Acc);
  449. %% case where you have `foo ,bar`
  450. arg_or_flag(["," ++ Task|Rest], Acc) -> new_task([Task|Rest], Acc);
  451. %% a flag
  452. arg_or_flag(["-" ++ _ = Flag|Rest], [{Task, Args}|Acc]) ->
  453. case maybe_ends_in_comma(Flag) of
  454. false -> arg_or_flag(Rest, [{Task, [Flag|Args]}|Acc]);
  455. NewFlag -> new_task(Rest, [{Task,
  456. lists:reverse([NewFlag|Args])}|Acc])
  457. end;
  458. %% an argument or a sequence of arguments
  459. arg_or_flag([ArgList|Rest], [{Task, Args}|Acc]) ->
  460. case re:split(ArgList, ",", [{return, list}, {parts, 2}]) of
  461. %% single arg terminated by a comma
  462. [Arg, ""] -> new_task(Rest, [{Task,
  463. lists:reverse([Arg|Args])}|Acc]);
  464. %% sequence of two or more args/tasks
  465. [Arg, More] -> new_task([More|Rest], [{Task,
  466. lists:reverse([Arg|Args])}|Acc]);
  467. %% single arg not terminated by a comma
  468. [Arg] -> arg_or_flag(Rest, [{Task, [Arg|Args]}|Acc])
  469. end.
  470. maybe_ends_in_comma(H) ->
  471. case lists:reverse(H) of
  472. "," ++ Flag -> lists:reverse(Flag);
  473. _ -> false
  474. end.