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.

514 line
17 KiB

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