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.

517 lines
18 KiB

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