25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
16 KiB

14 년 전
14 년 전
14 년 전
13 년 전
14 년 전
14 년 전
  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 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).
  28. -export([main/1,
  29. run/2,
  30. help/0,
  31. parse_args/1,
  32. version/0,
  33. get_jobs/1]).
  34. -include("rebar.hrl").
  35. -ifndef(BUILD_TIME).
  36. -define(BUILD_TIME, "undefined").
  37. -endif.
  38. -ifndef(VCS_INFO).
  39. -define(VCS_INFO, "undefined").
  40. -endif.
  41. -ifndef(OTP_INFO).
  42. -define(OTP_INFO, "undefined").
  43. -endif.
  44. -define(DEFAULT_JOBS, 3).
  45. %% ====================================================================
  46. %% Public API
  47. %% ====================================================================
  48. %% escript Entry point
  49. main(Args) ->
  50. case catch(run(Args)) of
  51. ok ->
  52. ok;
  53. rebar_abort ->
  54. rebar_utils:delayed_halt(1);
  55. Error ->
  56. %% Nothing should percolate up from rebar_core;
  57. %% Dump this error to console
  58. io:format("Uncaught error in rebar_core: ~p\n", [Error]),
  59. rebar_utils:delayed_halt(1)
  60. end.
  61. %% Erlang-API entry point
  62. run(BaseConfig, Commands) ->
  63. _ = application:load(rebar),
  64. run_aux(BaseConfig, Commands).
  65. %% ====================================================================
  66. %% Internal functions
  67. %% ====================================================================
  68. run(["help"]) ->
  69. help();
  70. run(["version"]) ->
  71. ok = load_rebar_app(),
  72. %% Display vsn and build time info
  73. version();
  74. run(RawArgs) ->
  75. ok = load_rebar_app(),
  76. %% Parse out command line arguments -- what's left is a list of commands to
  77. %% run -- and start running commands
  78. Args = parse_args(RawArgs),
  79. BaseConfig = init_config(Args),
  80. {BaseConfig1, Cmds} = save_options(BaseConfig, Args),
  81. case rebar_config:get_xconf(BaseConfig1, enable_profiling, false) of
  82. true ->
  83. io:format("Profiling!\n"),
  84. try
  85. fprof:apply(fun run_aux/2, [BaseConfig1, Cmds])
  86. after
  87. ok = fprof:profile(),
  88. ok = fprof:analyse([{dest, "fprof.analysis"}])
  89. end;
  90. false ->
  91. run_aux(BaseConfig1, Cmds)
  92. end.
  93. load_rebar_app() ->
  94. %% Pre-load the rebar app so that we get default configuration
  95. ok = application:load(rebar).
  96. init_config({Options, _NonOptArgs}) ->
  97. %% If $HOME/.rebar/config exists load and use as global config
  98. GlobalConfigFile = filename:join([os:getenv("HOME"), ".rebar", "config"]),
  99. GlobalConfig = case filelib:is_regular(GlobalConfigFile) of
  100. true ->
  101. ?DEBUG("Load global config file ~p~n",
  102. [GlobalConfigFile]),
  103. rebar_config:new(GlobalConfigFile);
  104. false ->
  105. rebar_config:new()
  106. end,
  107. %% Set the rebar config to use
  108. GlobalConfig1 = case proplists:get_value(config, Options) of
  109. undefined ->
  110. GlobalConfig;
  111. Conf ->
  112. rebar_config:set_global(GlobalConfig, config, Conf)
  113. end,
  114. GlobalConfig2 = set_log_level(GlobalConfig1, Options),
  115. %% Initialize logging system
  116. ok = rebar_log:init(GlobalConfig2),
  117. BaseConfig = rebar_config:base_config(GlobalConfig2),
  118. %% Keep track of how many operations we do, so we can detect bad commands
  119. BaseConfig1 = rebar_config:set_xconf(BaseConfig, operations, 0),
  120. %% Initialize vsn cache
  121. rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()).
  122. run_aux(BaseConfig, Commands) ->
  123. %% Make sure crypto is running
  124. case crypto:start() of
  125. ok -> ok;
  126. {error,{already_started,crypto}} -> ok
  127. end,
  128. %% Convert command strings to atoms
  129. CommandAtoms = [list_to_atom(C) || C <- Commands],
  130. %% Determine the location of the rebar executable; important for pulling
  131. %% resources out of the escript
  132. ScriptName = filename:absname(escript:script_name()),
  133. BaseConfig1 = rebar_config:set_xconf(BaseConfig, escript, ScriptName),
  134. ?DEBUG("Rebar location: ~p\n", [ScriptName]),
  135. %% Note the top-level directory for reference
  136. AbsCwd = filename:absname(rebar_utils:get_cwd()),
  137. BaseConfig2 = rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd),
  138. %% Process each command, resetting any state between each one
  139. rebar_core:process_commands(CommandAtoms, BaseConfig2).
  140. %%
  141. %% print help/usage string
  142. %%
  143. help() ->
  144. OptSpecList = option_spec_list(),
  145. getopt:usage(OptSpecList, "rebar",
  146. "[var=value,...] <command,...>",
  147. [{"var=value", "rebar global variables (e.g. force=1)"},
  148. {"command", "Command to run (e.g. compile)"}]).
  149. %%
  150. %% Parse command line arguments using getopt and also filtering out any
  151. %% key=value pairs. What's left is the list of commands to run
  152. %%
  153. parse_args(RawArgs) ->
  154. %% Parse getopt options
  155. OptSpecList = option_spec_list(),
  156. case getopt:parse(OptSpecList, RawArgs) of
  157. {ok, Args} ->
  158. Args;
  159. {error, {Reason, Data}} ->
  160. ?ERROR("~s ~p~n~n", [Reason, Data]),
  161. help(),
  162. rebar_utils:delayed_halt(1)
  163. end.
  164. save_options(Config, {Options, NonOptArgs}) ->
  165. %% Check options and maybe halt execution
  166. ok = show_info_maybe_halt(Options, NonOptArgs),
  167. GlobalDefines = proplists:get_all_values(defines, Options),
  168. Config1 = rebar_config:set_xconf(Config, defines, GlobalDefines),
  169. %% Setup profiling flag
  170. Config2 = rebar_config:set_xconf(Config1, enable_profiling,
  171. proplists:get_bool(profile, Options)),
  172. %% Setup flag to keep running after a single command fails
  173. Config3 = rebar_config:set_xconf(Config2, keep_going,
  174. proplists:get_bool(keep_going, Options)),
  175. %% Set global variables based on getopt options
  176. Config4 = set_global_flag(Config3, Options, force),
  177. Config5 = case proplists:get_value(jobs, Options, ?DEFAULT_JOBS) of
  178. ?DEFAULT_JOBS ->
  179. Config4;
  180. Jobs ->
  181. rebar_config:set_global(Config4, jobs, Jobs)
  182. end,
  183. %% Filter all the flags (i.e. strings of form key=value) from the
  184. %% command line arguments. What's left will be the commands to run.
  185. {Config6, RawCmds} = filter_flags(Config5, NonOptArgs, []),
  186. {Config6, unabbreviate_command_names(RawCmds)}.
  187. %%
  188. %% set log level based on getopt option
  189. %%
  190. set_log_level(Config, Options) ->
  191. LogLevel = case proplists:get_all_values(verbose, Options) of
  192. [] ->
  193. rebar_log:default_level();
  194. Verbosities ->
  195. lists:last(Verbosities)
  196. end,
  197. rebar_config:set_global(Config, verbose, LogLevel).
  198. %%
  199. %% show version information and halt
  200. %%
  201. version() ->
  202. {ok, Vsn} = application:get_key(rebar, vsn),
  203. ?CONSOLE("rebar ~s ~s ~s ~s\n",
  204. [Vsn, ?OTP_INFO, ?BUILD_TIME, ?VCS_INFO]).
  205. %%
  206. %% set global flag based on getopt option boolean value
  207. %%
  208. set_global_flag(Config, Options, Flag) ->
  209. Value = case proplists:get_bool(Flag, Options) of
  210. true ->
  211. "1";
  212. false ->
  213. "0"
  214. end,
  215. rebar_config:set_global(Config, Flag, Value).
  216. %%
  217. %% show info and maybe halt execution
  218. %%
  219. show_info_maybe_halt(Opts, NonOptArgs) ->
  220. false = show_info_maybe_halt(help, Opts, fun help/0),
  221. false = show_info_maybe_halt(commands, Opts, fun commands/0),
  222. false = show_info_maybe_halt(version, Opts, fun version/0),
  223. case NonOptArgs of
  224. [] ->
  225. ?CONSOLE("No command to run specified!~n",[]),
  226. help(),
  227. rebar_utils:delayed_halt(1);
  228. _ ->
  229. ok
  230. end.
  231. show_info_maybe_halt(O, Opts, F) ->
  232. case proplists:get_bool(O, Opts) of
  233. true ->
  234. F(),
  235. rebar_utils:delayed_halt(0);
  236. false ->
  237. false
  238. end.
  239. %%
  240. %% print known commands
  241. %%
  242. commands() ->
  243. S = <<"
  244. clean Clean
  245. compile Compile sources
  246. escriptize Generate escript archive
  247. create template= [var=foo,...] Create skel based on template and vars
  248. create-app [appid=myapp] Create simple app skel
  249. create-node [nodeid=mynode] Create simple node skel
  250. list-templates List available templates
  251. doc Generate Erlang program documentation
  252. check-deps Display to be fetched dependencies
  253. get-deps Fetch dependencies
  254. update-deps Update fetched dependencies
  255. delete-deps Delete fetched dependencies
  256. list-deps List dependencies
  257. generate [dump_spec=0/1] Build release with reltool
  258. overlay Run reltool overlays only
  259. generate-upgrade previous_release=path Build an upgrade package
  260. generate-appups previous_release=path Generate appup files
  261. eunit [suites=foo] Run eunit tests in foo.erl and
  262. test/foo_tests.erl
  263. [suites=foo] [tests=bar] Run specific eunit tests [first test name
  264. starting with 'bar' in foo.erl and
  265. test/foo_tests.erl]
  266. [tests=bar] For every existing suite, run the first
  267. test whose name starts with bar and, if
  268. no such test exists, run the test whose
  269. name starts with bar in the suite's
  270. _tests module
  271. ct [suites=] [case=] Run common_test suites
  272. qc Test QuickCheck properties
  273. xref Run cross reference analysis
  274. help Show the program options
  275. version Show version information
  276. ">>,
  277. io:put_chars(S).
  278. get_jobs(Config) ->
  279. rebar_config:get_global(Config, jobs, ?DEFAULT_JOBS).
  280. %%
  281. %% options accepted via getopt
  282. %%
  283. option_spec_list() ->
  284. Jobs = ?DEFAULT_JOBS,
  285. JobsHelp = io_lib:format(
  286. "Number of concurrent workers a command may use. Default: ~B",
  287. [Jobs]),
  288. VerboseHelp = "Verbosity level (-v, -vv, -vvv, --verbose 3). Default: 0",
  289. [
  290. %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
  291. {help, $h, "help", undefined, "Show the program options"},
  292. {commands, $c, "commands", undefined, "Show available commands"},
  293. {verbose, $v, "verbose", integer, VerboseHelp},
  294. {version, $V, "version", undefined, "Show version information"},
  295. {force, $f, "force", undefined, "Force"},
  296. {defines, $D, undefined, string, "Define compiler macro"},
  297. {jobs, $j, "jobs", integer, JobsHelp},
  298. {config, $C, "config", string, "Rebar config file to use"},
  299. {profile, $p, "profile", undefined, "Profile this run of rebar"},
  300. {keep_going, $k, "keep-going", undefined,
  301. "Keep running after a command fails"}
  302. ].
  303. %%
  304. %% Seperate all commands (single-words) from flags (key=value) and store
  305. %% values into the rebar_config global storage.
  306. %%
  307. filter_flags(Config, [], Commands) ->
  308. {Config, lists:reverse(Commands)};
  309. filter_flags(Config, [Item | Rest], Commands) ->
  310. case string:tokens(Item, "=") of
  311. [Command] ->
  312. filter_flags(Config, Rest, [Command | Commands]);
  313. [KeyStr, RawValue] ->
  314. Key = list_to_atom(KeyStr),
  315. Value = case Key of
  316. verbose ->
  317. list_to_integer(RawValue);
  318. _ ->
  319. RawValue
  320. end,
  321. Config1 = rebar_config:set_global(Config, Key, Value),
  322. filter_flags(Config1, Rest, Commands);
  323. Other ->
  324. ?CONSOLE("Ignoring command line argument: ~p\n", [Other]),
  325. filter_flags(Config, Rest, Commands)
  326. end.
  327. command_names() ->
  328. ["check-deps", "clean", "compile", "create", "create-app", "create-node",
  329. "ct", "delete-deps", "doc", "eunit", "escriptize", "generate",
  330. "generate-appups", "generate-upgrade", "get-deps", "help", "list-deps",
  331. "list-templates", "qc", "update-deps", "overlay", "shell", "version",
  332. "xref"].
  333. unabbreviate_command_names([]) ->
  334. [];
  335. unabbreviate_command_names([Command | Commands]) ->
  336. case get_command_name_candidates(Command) of
  337. [] ->
  338. %% let the rest of the code detect that the command doesn't exist
  339. %% (this would perhaps be a good place to fail)
  340. [Command | unabbreviate_command_names(Commands)];
  341. [FullCommand] ->
  342. [FullCommand | unabbreviate_command_names(Commands)];
  343. Candidates ->
  344. ?ABORT("Found more than one match for abbreviated command name "
  345. " '~s',~nplease be more specific. Possible candidates:~n"
  346. " ~s~n",
  347. [Command, string:join(Candidates, ", ")])
  348. end.
  349. get_command_name_candidates(Command) ->
  350. %% Get the command names which match the given (abbreviated) command name.
  351. %% * "c" matches commands like compile, clean and create-app
  352. %% * "create" matches command create only, since it's unique
  353. %% * "create-" matches commands starting with create-
  354. %% * "c-a" matches create-app
  355. %% * "create-a" matches create-app
  356. %% * "c-app" matches create-app
  357. Candidates = [Candidate || Candidate <- command_names(),
  358. is_command_name_candidate(Command, Candidate)],
  359. %% Is there a complete match? If so return only that, return a
  360. %% list of candidates otherwise
  361. case lists:member(Command, Candidates) of
  362. true -> [Command];
  363. false -> Candidates
  364. end.
  365. is_command_name_candidate(Command, Candidate) ->
  366. lists:prefix(Command, Candidate)
  367. orelse is_command_name_sub_word_candidate(Command, Candidate).
  368. is_command_name_sub_word_candidate(Command, Candidate) ->
  369. %% Allow for parts of commands to be abbreviated, i.e. create-app
  370. %% can be shortened to "create-a", "c-a" or "c-app" (but not
  371. %% "create-" since that would be ambiguous).
  372. ReOpts = [{return, list}],
  373. CommandSubWords = re:split(Command, "-", ReOpts),
  374. CandidateSubWords = re:split(Candidate, "-", ReOpts),
  375. is_command_name_sub_word_candidate_aux(CommandSubWords, CandidateSubWords).
  376. is_command_name_sub_word_candidate_aux([CmdSW | CmdSWs],
  377. [CandSW | CandSWs]) ->
  378. lists:prefix(CmdSW, CandSW) andalso
  379. is_command_name_sub_word_candidate_aux(CmdSWs, CandSWs);
  380. is_command_name_sub_word_candidate_aux([], []) ->
  381. true;
  382. is_command_name_sub_word_candidate_aux(_CmdSWs, _CandSWs) ->
  383. false.