Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

520 rader
18 KiB

15 år sedan
15 år sedan
15 år sedan
13 år sedan
13 år sedan
  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(\\s|$)|{~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_and_abort_on_error) ->
  276. {error_handler,
  277. fun debug_and_abort/2};
  278. expand_sh_flag(use_stdout) ->
  279. {output_handler,
  280. fun(Line, Acc) ->
  281. %% Line already has a newline so don't use ?CONSOLE which adds one
  282. io:format("~s", [Line]),
  283. [Line | Acc]
  284. end};
  285. expand_sh_flag({use_stdout, false}) ->
  286. {output_handler,
  287. fun(Line, Acc) ->
  288. [Line | Acc]
  289. end};
  290. expand_sh_flag({cd, _CdArg} = Cd) ->
  291. {port_settings, Cd};
  292. expand_sh_flag({env, _EnvArg} = Env) ->
  293. {port_settings, Env}.
  294. -type err_handler() :: fun((string(), {integer(), string()}) -> no_return()).
  295. -spec log_msg_and_abort(string()) -> err_handler().
  296. log_msg_and_abort(Message) ->
  297. fun(_Command, {_Rc, _Output}) ->
  298. ?ABORT(Message, [])
  299. end.
  300. -spec log_and_abort(string(), {integer(), string()}) -> no_return().
  301. log_and_abort(Command, {Rc, Output}) ->
  302. ?ABORT("sh(~s)~n"
  303. "failed with return code ~w and the following output:~n"
  304. "~s", [Command, Rc, Output]).
  305. -spec debug_and_abort(string(), {integer(), string()}) -> no_return().
  306. debug_and_abort(Command, {Rc, Output}) ->
  307. ?DEBUG("sh(~s)~n"
  308. "failed with return code ~w and the following output:~n"
  309. "~s", [Command, Rc, Output]),
  310. throw(rebar_abort).
  311. sh_loop(Port, Fun, Acc) ->
  312. receive
  313. {Port, {data, {eol, Line}}} ->
  314. sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
  315. {Port, {data, {noeol, Line}}} ->
  316. sh_loop(Port, Fun, Fun(Line, Acc));
  317. {Port, {exit_status, 0}} ->
  318. {ok, lists:flatten(lists:reverse(Acc))};
  319. {Port, {exit_status, Rc}} ->
  320. {error, {Rc, lists:flatten(lists:reverse(Acc))}}
  321. end.
  322. beam_to_mod(Dir, Filename) ->
  323. [Dir | Rest] = filename:split(Filename),
  324. list_to_atom(filename:basename(string:join(Rest, "."), ".beam")).
  325. beam_to_mod(Filename) ->
  326. list_to_atom(filename:basename(Filename, ".beam")).
  327. erl_to_mod(Filename) ->
  328. list_to_atom(filename:rootname(filename:basename(Filename))).
  329. beams(Dir) ->
  330. filelib:fold_files(Dir, ".*\.beam\$", true,
  331. fun(F, Acc) -> [F | Acc] end, []).
  332. -spec abort() -> no_return().
  333. abort() ->
  334. throw(rebar_abort).
  335. -spec abort(string(), [term()]) -> no_return().
  336. abort(String, Args) ->
  337. ?ERROR(String, Args),
  338. abort().
  339. escript_foldl(Fun, Acc, File) ->
  340. case escript:extract(File, [compile_source]) of
  341. {ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
  342. case Body of
  343. {source, BeamCode} ->
  344. GetInfo = fun() -> file:read_file_info(File) end,
  345. GetBin = fun() -> BeamCode end,
  346. {ok, Fun(".", GetInfo, GetBin, Acc)};
  347. {beam, BeamCode} ->
  348. GetInfo = fun() -> file:read_file_info(File) end,
  349. GetBin = fun() -> BeamCode end,
  350. {ok, Fun(".", GetInfo, GetBin, Acc)};
  351. {archive, ArchiveBin} ->
  352. zip:foldl(Fun, Acc, {File, ArchiveBin})
  353. end;
  354. {error, _} = Error ->
  355. Error
  356. end.
  357. vcs_vsn(Vcs, Dir) ->
  358. case vcs_vsn_cmd(Vcs, Dir) of
  359. {plain, VsnString} ->
  360. VsnString;
  361. {cmd, CmdString} ->
  362. vcs_vsn_invoke(CmdString, Dir);
  363. unknown ->
  364. ?ABORT("vcs_vsn: Unknown vsn format: ~p", [Vcs]);
  365. {error, Reason} ->
  366. ?ABORT("vcs_vsn: ~s", [Reason])
  367. end.
  368. %% Temp work around for repos like relx that use "semver"
  369. vcs_vsn_cmd(VCS, Dir) when VCS =:= semver ; VCS =:= "semver" ->
  370. rebar_git_resource:make_vsn(Dir);
  371. vcs_vsn_cmd(VCS, Dir) when VCS =:= git ; VCS =:= "git" ->
  372. rebar_git_resource:make_vsn(Dir);
  373. vcs_vsn_cmd(VCS, Dir) when VCS =:= pkg ; VCS =:= "pkg" ->
  374. rebar_pkg_resource:make_vsn(Dir);
  375. vcs_vsn_cmd({cmd, _Cmd}=Custom, _) ->
  376. Custom;
  377. vcs_vsn_cmd(Version, _) when is_list(Version) ->
  378. {plain, Version};
  379. vcs_vsn_cmd(_, _) ->
  380. unknown.
  381. vcs_vsn_invoke(Cmd, Dir) ->
  382. {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
  383. string:strip(VsnString, right, $\n).
  384. %%
  385. %% Filter a list of erl_opts platform_define options such that only
  386. %% those which match the provided architecture regex are returned.
  387. %%
  388. filter_defines([], Acc) ->
  389. lists:reverse(Acc);
  390. filter_defines([{platform_define, ArchRegex, Key} | Rest], Acc) ->
  391. case rebar_utils:is_arch(ArchRegex) of
  392. true ->
  393. filter_defines(Rest, [{d, Key} | Acc]);
  394. false ->
  395. filter_defines(Rest, Acc)
  396. end;
  397. filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) ->
  398. case rebar_utils:is_arch(ArchRegex) of
  399. true ->
  400. filter_defines(Rest, [{d, Key, Value} | Acc]);
  401. false ->
  402. filter_defines(Rest, Acc)
  403. end;
  404. filter_defines([Opt | Rest], Acc) ->
  405. filter_defines(Rest, [Opt | Acc]).
  406. %% @doc ident to the level specified
  407. -spec indent(non_neg_integer()) -> iolist().
  408. indent(Amount) when erlang:is_integer(Amount) ->
  409. [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
  410. cleanup_code_path(OrigPath) ->
  411. CurrentPath = code:get_path(),
  412. AddedPaths = CurrentPath -- OrigPath,
  413. %% If someone has removed paths, it's hard to get them back into
  414. %% the right order, but since this is currently rare, we can just
  415. %% fall back to code:set_path/1.
  416. case CurrentPath -- AddedPaths of
  417. OrigPath ->
  418. _ = [code:del_path(Path) || Path <- AddedPaths],
  419. true;
  420. _ ->
  421. code:set_path(OrigPath)
  422. end.
  423. new_task([], Acc) -> lists:reverse(Acc);
  424. new_task([TaskList|Rest], Acc) ->
  425. case re:split(TaskList, ",", [{return, list}, {parts, 2}]) of
  426. %% `do` consumes all remaining args
  427. ["do" = Task|RestArgs] ->
  428. lists:reverse([{Task, RestArgs ++ Rest}|Acc]);
  429. %% single task terminated by a comma
  430. [Task, ""] -> new_task(Rest, [{Task, []}|Acc]);
  431. %% sequence of two or more tasks
  432. [Task, More] -> new_task([More|Rest], [{Task, []}|Acc]);
  433. %% single task not terminated by a comma
  434. [Task] -> arg_or_flag(Rest, [{Task, []}|Acc])
  435. end.
  436. arg_or_flag([], [{Task, Args}|Acc]) ->
  437. lists:reverse([{Task, lists:reverse(Args)}|Acc]);
  438. %% case where you have `foo , bar`
  439. arg_or_flag([","|Rest], Acc) -> new_task(Rest, Acc);
  440. %% case where you have `foo ,bar`
  441. arg_or_flag(["," ++ Task|Rest], Acc) -> new_task([Task|Rest], Acc);
  442. %% a flag
  443. arg_or_flag(["-" ++ _ = Flag|Rest], [{Task, Args}|Acc]) ->
  444. case maybe_ends_in_comma(Flag) of
  445. false -> arg_or_flag(Rest, [{Task, [Flag|Args]}|Acc]);
  446. NewFlag -> new_task(Rest, [{Task,
  447. lists:reverse([NewFlag|Args])}|Acc])
  448. end;
  449. %% an argument or a sequence of arguments
  450. arg_or_flag([ArgList|Rest], [{Task, Args}|Acc]) ->
  451. case re:split(ArgList, ",", [{return, list}, {parts, 2}]) of
  452. %% single arg terminated by a comma
  453. [Arg, ""] -> new_task(Rest, [{Task,
  454. lists:reverse([Arg|Args])}|Acc]);
  455. %% sequence of two or more args/tasks
  456. [Arg, More] -> new_task([More|Rest], [{Task,
  457. lists:reverse([Arg|Args])}|Acc]);
  458. %% single arg not terminated by a comma
  459. [Arg] -> arg_or_flag(Rest, [{Task, [Arg|Args]}|Acc])
  460. end.
  461. maybe_ends_in_comma(H) ->
  462. case lists:reverse(H) of
  463. "," ++ Flag -> lists:reverse(Flag);
  464. _ -> false
  465. end.