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.

405 lines
16 KiB

10 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. %%
  28. %% @doc Main module for rebar3. Supports two interfaces; one for escripts,
  29. %% and one for usage as a library (although rebar3 makes a lot of
  30. %% assumptions about its environment, making it a bit tricky to use as
  31. %% a lib).
  32. %%
  33. %% This module's job is mostly to set up the root environment for rebar3
  34. %% and handle global options (mostly all from the ENV) and make them
  35. %% accessible to the rest of the run.
  36. %% @end
  37. -module(rebar3).
  38. -export([main/0,
  39. main/1,
  40. run/1,
  41. run/2,
  42. global_option_spec_list/0,
  43. init_config/0,
  44. set_options/2,
  45. parse_args/1,
  46. version/0,
  47. log_level/0]).
  48. -include("rebar.hrl").
  49. %% ====================================================================
  50. %% Public API
  51. %% ====================================================================
  52. %% @doc For running with:
  53. %% erl +sbtu +A0 -noinput -mode minimal -boot start_clean -s rebar3 main -extra "$@"
  54. -spec main() -> no_return().
  55. main() ->
  56. List = init:get_plain_arguments(),
  57. main(List).
  58. %% @doc escript Entry point
  59. -spec main(list()) -> no_return().
  60. main(Args) ->
  61. try run(Args) of
  62. {ok, _State} ->
  63. erlang:halt(0);
  64. Error ->
  65. handle_error(Error)
  66. catch
  67. _:Error ->
  68. handle_error(Error)
  69. end.
  70. %% @doc Erlang-API entry point
  71. -spec run(rebar_state:t(), [string()]) -> {ok, rebar_state:t()} | {error, term()}.
  72. run(BaseState, Commands) ->
  73. start_and_load_apps(api),
  74. BaseState1 = rebar_state:set(BaseState, task, Commands),
  75. BaseState2 = rebar_state:set(BaseState1, caller, api),
  76. Verbosity = log_level(),
  77. ok = rebar_log:init(api, Verbosity),
  78. run_aux(BaseState2, Commands).
  79. %% ====================================================================
  80. %% Internal functions
  81. %% ====================================================================
  82. %% @private sets up the rebar3 environment based on the command line
  83. %% arguments passed, if they have any relevance; used to translate
  84. %% from the escript call-site into a common one with the library
  85. %% usage.
  86. run(RawArgs) ->
  87. start_and_load_apps(command_line),
  88. BaseState = init_config(),
  89. BaseState1 = rebar_state:set(BaseState, caller, command_line),
  90. case erlang:system_info(version) of
  91. "6.1" ->
  92. ?WARN("Due to a filelib bug in Erlang 17.1 it is recommended"
  93. "you update to a newer release.", []);
  94. _ ->
  95. ok
  96. end,
  97. {BaseState2, _Args1} = set_options(BaseState1, {[], []}),
  98. run_aux(BaseState2, RawArgs).
  99. %% @private Junction point between the CLI and library entry points.
  100. %% From here on the module's role is a shared path here to finish
  101. %% up setting the environment for the run.
  102. -spec run_aux(rebar_state:t(), [string()]) ->
  103. {ok, rebar_state:t()} | {error, term()}.
  104. run_aux(State, RawArgs) ->
  105. %% Profile override; can only support one profile
  106. State1 = case os:getenv("REBAR_PROFILE") of
  107. false ->
  108. State;
  109. "" ->
  110. State;
  111. Profile ->
  112. rebar_state:apply_profiles(State, [list_to_atom(Profile)])
  113. end,
  114. rebar_utils:check_min_otp_version(rebar_state:get(State1, minimum_otp_vsn, undefined)),
  115. rebar_utils:check_blacklisted_otp_versions(rebar_state:get(State1, blacklisted_otp_vsns, undefined)),
  116. %% Change the default hex CDN
  117. State2 = case os:getenv("HEX_CDN") of
  118. false ->
  119. State1;
  120. CDN ->
  121. rebar_state:set(State1, rebar_packages_cdn, CDN)
  122. end,
  123. %% bootstrap test profile
  124. State3 = rebar_state:add_to_profile(State2, test, test_state(State1)),
  125. %% Process each command, resetting any state between each one
  126. BaseDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR),
  127. State4 = rebar_state:set(State3, base_dir,
  128. filename:join(filename:absname(rebar_state:dir(State3)), BaseDir)),
  129. {ok, Providers} = application:get_env(rebar, providers),
  130. %% Providers can modify profiles stored in opts, so set default after initializing providers
  131. State5 = rebar_state:create_logic_providers(Providers, State4),
  132. %% Initializing project_plugins which can override default providers
  133. State6 = rebar_plugins:project_plugins_install(State5),
  134. State7 = rebar_plugins:top_level_install(State6),
  135. State8 = case os:getenv("REBAR_CACHE_DIR") of
  136. false ->
  137. State7;
  138. ConfigFile ->
  139. rebar_state:set(State7, global_rebar_dir, ConfigFile)
  140. end,
  141. State9 = rebar_state:default(State8, rebar_state:opts(State8)),
  142. {Task, Args} = parse_args(RawArgs),
  143. State10 = rebar_state:code_paths(State9, default, code:get_path()),
  144. rebar_core:init_command(rebar_state:command_args(State10, Args), Task).
  145. %% @doc set up base configuration having to do with verbosity, where
  146. %% to find config files, and so on, and return an internal rebar3 state term.
  147. -spec init_config() -> rebar_state:t().
  148. init_config() ->
  149. %% Initialize logging system
  150. Verbosity = log_level(),
  151. ok = rebar_log:init(command_line, Verbosity),
  152. Config = rebar_config:consult(),
  153. Config1 = rebar_config:merge_locks(Config, rebar_config:consult_lock_file(?LOCK_FILE)),
  154. %% If $HOME/.config/rebar3/rebar.config exists load and use as global config
  155. GlobalConfigFile = rebar_dir:global_config(),
  156. State = case filelib:is_regular(GlobalConfigFile) of
  157. true ->
  158. ?DEBUG("Load global config file ~s", [GlobalConfigFile]),
  159. try state_from_global_config(Config1, GlobalConfigFile)
  160. catch
  161. _:_ ->
  162. ?WARN("Global config ~s exists but can not be read. Ignoring global config values.", [GlobalConfigFile]),
  163. rebar_state:new(Config1)
  164. end;
  165. false ->
  166. rebar_state:new(Config1)
  167. end,
  168. %% Determine the location of the rebar executable; important for pulling
  169. %% resources out of the escript
  170. State1 = try
  171. ScriptName = filename:absname(escript:script_name()),
  172. %% Running with 'erl -s rebar3 main' still sets a name for some reason
  173. %% so verify it is a real file
  174. case filelib:is_regular(ScriptName) of
  175. true ->
  176. rebar_state:escript_path(State, ScriptName);
  177. false ->
  178. State
  179. end
  180. catch
  181. _:_ ->
  182. State
  183. end,
  184. %% TODO: Do we need this still? I think it may still be used.
  185. %% Initialize vsn cache
  186. rebar_state:set(State1, vsn_cache, dict:new()).
  187. %% @doc Parse basic rebar3 arguments to find the top-level task
  188. %% to be run; this parsing is only partial from the point of view that
  189. %% runs done with arguments like `as $PROFILE do $TASK' will just
  190. %% return `as', which is then in charge of doing a more dynamic
  191. %% dispatch.
  192. %% If no arguments are given, the `help' task is returned.
  193. %% If special arguments like `-h' or `-v' are translated to `help'
  194. %% and `version' tasks.
  195. %% The unparsed parts of arguments are returned in:
  196. %% `{Task, Rest}'.
  197. -spec parse_args([string()]) -> {atom(), [string()]}.
  198. parse_args([]) ->
  199. parse_args(["help"]);
  200. parse_args([H | Rest]) when H =:= "-h"
  201. ; H =:= "--help" ->
  202. parse_args(["help" | Rest]);
  203. parse_args([H | Rest]) when H =:= "-v"
  204. ; H =:= "--version" ->
  205. parse_args(["version" | Rest]);
  206. parse_args([Task | RawRest]) ->
  207. {list_to_atom(Task), RawRest}.
  208. %% @private actually not too sure what this does anymore.
  209. set_options(State, {Options, NonOptArgs}) ->
  210. GlobalDefines = proplists:get_all_values(defines, Options),
  211. State1 = rebar_state:set(State, defines, GlobalDefines),
  212. %% Set global variables based on getopt options
  213. State2 = set_global_flag(State1, Options, force),
  214. Task = proplists:get_value(task, Options, "help"),
  215. {rebar_state:set(State2, task, Task), NonOptArgs}.
  216. %% @doc get log level based on getopt options and ENV
  217. -spec log_level() -> integer().
  218. log_level() ->
  219. case os:getenv("QUIET") of
  220. Q when Q == false; Q == "" ->
  221. DefaultLevel = rebar_log:default_level(),
  222. case os:getenv("DEBUG") of
  223. D when D == false; D == "" ->
  224. DefaultLevel;
  225. _ ->
  226. DefaultLevel + 3
  227. end;
  228. _ ->
  229. rebar_log:error_level()
  230. end.
  231. %% @doc show version information
  232. -spec version() -> ok.
  233. version() ->
  234. {ok, Vsn} = application:get_key(rebar, vsn),
  235. ?CONSOLE("rebar ~s on Erlang/OTP ~s Erts ~s",
  236. [Vsn, erlang:system_info(otp_release), erlang:system_info(version)]).
  237. %% @private set global flag based on getopt option boolean value
  238. %% TODO: Actually make it 'global'
  239. -spec set_global_flag(rebar_state:t(), list(), term()) -> rebar_state:t().
  240. set_global_flag(State, Options, Flag) ->
  241. Value = case proplists:get_bool(Flag, Options) of
  242. true ->
  243. "1";
  244. false ->
  245. "0"
  246. end,
  247. rebar_state:set(State, Flag, Value).
  248. %% @doc options accepted via getopt
  249. -spec global_option_spec_list() -> [{atom(), char(), string(), atom(), string()}, ...].
  250. global_option_spec_list() ->
  251. [
  252. %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
  253. {help, $h, "help", undefined, "Print this help."},
  254. {version, $v, "version", undefined, "Show version information."},
  255. {task, undefined, undefined, string, "Task to run."}
  256. ].
  257. %% @private translate unhandled errors and internal return codes into proper
  258. %% erroneous program exits.
  259. -spec handle_error(term()) -> no_return().
  260. handle_error(rebar_abort) ->
  261. erlang:halt(1);
  262. handle_error({error, rebar_abort}) ->
  263. erlang:halt(1);
  264. handle_error({error, {Module, Reason}}) ->
  265. case code:which(Module) of
  266. non_existing ->
  267. ?CRASHDUMP("~p: ~p~n~p~n~n", [Module, Reason, erlang:get_stacktrace()]),
  268. ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to stacktrace or consult rebar3.crashdump", []),
  269. ?DEBUG("Uncaught error: ~p ~p", [Module, Reason]),
  270. ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []);
  271. _ ->
  272. ?ERROR("~s", [Module:format_error(Reason)])
  273. end,
  274. erlang:halt(1);
  275. handle_error({error, Error}) when is_list(Error) ->
  276. ?ERROR("~s", [Error]),
  277. erlang:halt(1);
  278. handle_error(Error) ->
  279. %% Nothing should percolate up from rebar_core;
  280. %% Dump this error to console
  281. StackTrace = erlang:get_stacktrace(),
  282. ?CRASHDUMP("Error: ~p~n~p~n~n", [Error, StackTrace]),
  283. ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace or consult rebar3.crashdump", []),
  284. ?DEBUG("Uncaught error: ~p", [Error]),
  285. case StackTrace of
  286. [] -> ok;
  287. Trace ->
  288. ?DEBUG("Stack trace to the error location:~n~p", [Trace])
  289. end,
  290. ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []),
  291. erlang:halt(1).
  292. %% @private Boot Erlang dependencies; problem is that escripts don't auto-boot
  293. %% stuff the way releases do and we have to do it by hand.
  294. %% This also lets us detect and show nicer errors when a critical lib is
  295. %% not supported
  296. -spec start_and_load_apps(command_line|api) -> term().
  297. start_and_load_apps(Caller) ->
  298. _ = application:load(rebar),
  299. %% Make sure crypto is running
  300. ensure_running(crypto, Caller),
  301. ensure_running(asn1, Caller),
  302. ensure_running(public_key, Caller),
  303. ensure_running(ssl, Caller),
  304. inets:start(),
  305. inets:start(httpc, [{profile, rebar}]).
  306. %% @doc Make sure a required app is running, or display an error message
  307. %% and abort if there's a problem.
  308. -spec ensure_running(atom(), command_line|api) -> ok | no_return().
  309. ensure_running(App, Caller) ->
  310. case application:start(App) of
  311. ok -> ok;
  312. {error, {already_started, App}} -> ok;
  313. {error, Reason} ->
  314. %% These errors keep rebar3's own configuration to be loaded,
  315. %% which disables the log level and causes a failure without
  316. %% showing the error message. Bypass this entirely by overriding
  317. %% the default value (which allows logging to take place)
  318. %% and shut things down manually.
  319. Log = ec_cmd_log:new(warn, Caller),
  320. ec_cmd_log:error(Log, "Rebar dependency ~p could not be loaded "
  321. "for reason ~p~n", [App, Reason]),
  322. throw(rebar_abort)
  323. end.
  324. -spec state_from_global_config([term()], file:filename()) -> rebar_state:t().
  325. state_from_global_config(Config, GlobalConfigFile) ->
  326. rebar_utils:set_httpc_options(),
  327. GlobalConfigTerms = rebar_config:consult_file(GlobalConfigFile),
  328. GlobalConfig = rebar_state:new(GlobalConfigTerms),
  329. %% We don't want to worry about global plugin install state effecting later
  330. %% usage. So we throw away the global profile state used for plugin install.
  331. GlobalConfigThrowAway = rebar_state:current_profiles(GlobalConfig, [global]),
  332. GlobalState = case rebar_state:get(GlobalConfigThrowAway, plugins, []) of
  333. [] ->
  334. GlobalConfigThrowAway;
  335. GlobalPluginsToInstall ->
  336. rebar_plugins:handle_plugins(global,
  337. GlobalPluginsToInstall,
  338. GlobalConfigThrowAway)
  339. end,
  340. GlobalPlugins = rebar_state:providers(GlobalState),
  341. GlobalConfig2 = rebar_state:set(GlobalConfig, plugins, []),
  342. GlobalConfig3 = rebar_state:set(GlobalConfig2, {plugins, global}, rebar_state:get(GlobalConfigThrowAway, plugins, [])),
  343. rebar_state:providers(rebar_state:new(GlobalConfig3, Config), GlobalPlugins).
  344. test_state(State) ->
  345. ErlOpts = rebar_state:get(State, erl_opts, []),
  346. TestOpts = safe_define_test_macro(ErlOpts),
  347. [{extra_src_dirs, ["test"]}, {erl_opts, TestOpts}].
  348. safe_define_test_macro(Opts) ->
  349. %% defining a compile macro twice results in an exception so
  350. %% make sure 'TEST' is only defined once
  351. case test_defined(Opts) of
  352. true -> [];
  353. false -> [{d, 'TEST'}]
  354. end.
  355. test_defined([{d, 'TEST'}|_]) -> true;
  356. test_defined([{d, 'TEST', true}|_]) -> true;
  357. test_defined([_|Rest]) -> test_defined(Rest);
  358. test_defined([]) -> false.