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.

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