Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

530 wiersze
18 KiB

15 lat temu
15 lat temu
15 lat temu
12 lat temu
12 lat temu
  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.