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.

406 rivejä
16 KiB

10 vuotta sitten
9 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
  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. rebar_utils:set_httpc_options(),
  150. %% Initialize logging system
  151. Verbosity = log_level(),
  152. ok = rebar_log:init(command_line, Verbosity),
  153. Config = rebar_config:consult(),
  154. Config1 = rebar_config:merge_locks(Config, rebar_config:consult_lock_file(?LOCK_FILE)),
  155. %% If $HOME/.config/rebar3/rebar.config exists load and use as global config
  156. GlobalConfigFile = rebar_dir:global_config(),
  157. State = case filelib:is_regular(GlobalConfigFile) of
  158. true ->
  159. ?DEBUG("Load global config file ~s", [GlobalConfigFile]),
  160. try state_from_global_config(Config1, GlobalConfigFile)
  161. catch
  162. _:_ ->
  163. ?WARN("Global config ~s exists but can not be read. Ignoring global config values.", [GlobalConfigFile]),
  164. rebar_state:new(Config1)
  165. end;
  166. false ->
  167. rebar_state:new(Config1)
  168. end,
  169. %% Determine the location of the rebar executable; important for pulling
  170. %% resources out of the escript
  171. State1 = try
  172. ScriptName = filename:absname(escript:script_name()),
  173. %% Running with 'erl -s rebar3 main' still sets a name for some reason
  174. %% so verify it is a real file
  175. case filelib:is_regular(ScriptName) of
  176. true ->
  177. rebar_state:escript_path(State, ScriptName);
  178. false ->
  179. State
  180. end
  181. catch
  182. _:_ ->
  183. State
  184. end,
  185. %% TODO: Do we need this still? I think it may still be used.
  186. %% Initialize vsn cache
  187. rebar_state:set(State1, vsn_cache, dict:new()).
  188. %% @doc Parse basic rebar3 arguments to find the top-level task
  189. %% to be run; this parsing is only partial from the point of view that
  190. %% runs done with arguments like `as $PROFILE do $TASK' will just
  191. %% return `as', which is then in charge of doing a more dynamic
  192. %% dispatch.
  193. %% If no arguments are given, the `help' task is returned.
  194. %% If special arguments like `-h' or `-v' are translated to `help'
  195. %% and `version' tasks.
  196. %% The unparsed parts of arguments are returned in:
  197. %% `{Task, Rest}'.
  198. -spec parse_args([string()]) -> {atom(), [string()]}.
  199. parse_args([]) ->
  200. parse_args(["help"]);
  201. parse_args([H | Rest]) when H =:= "-h"
  202. ; H =:= "--help" ->
  203. parse_args(["help" | Rest]);
  204. parse_args([H | Rest]) when H =:= "-v"
  205. ; H =:= "--version" ->
  206. parse_args(["version" | Rest]);
  207. parse_args([Task | RawRest]) ->
  208. {list_to_atom(Task), RawRest}.
  209. %% @private actually not too sure what this does anymore.
  210. set_options(State, {Options, NonOptArgs}) ->
  211. GlobalDefines = proplists:get_all_values(defines, Options),
  212. State1 = rebar_state:set(State, defines, GlobalDefines),
  213. %% Set global variables based on getopt options
  214. State2 = set_global_flag(State1, Options, force),
  215. Task = proplists:get_value(task, Options, "help"),
  216. {rebar_state:set(State2, task, Task), NonOptArgs}.
  217. %% @doc get log level based on getopt options and ENV
  218. -spec log_level() -> integer().
  219. log_level() ->
  220. case os:getenv("QUIET") of
  221. Q when Q == false; Q == "" ->
  222. DefaultLevel = rebar_log:default_level(),
  223. case os:getenv("DEBUG") of
  224. D when D == false; D == "" ->
  225. DefaultLevel;
  226. _ ->
  227. DefaultLevel + 3
  228. end;
  229. _ ->
  230. rebar_log:error_level()
  231. end.
  232. %% @doc show version information
  233. -spec version() -> ok.
  234. version() ->
  235. {ok, Vsn} = application:get_key(rebar, vsn),
  236. ?CONSOLE("rebar ~s on Erlang/OTP ~s Erts ~s",
  237. [Vsn, erlang:system_info(otp_release), erlang:system_info(version)]).
  238. %% @private set global flag based on getopt option boolean value
  239. %% TODO: Actually make it 'global'
  240. -spec set_global_flag(rebar_state:t(), list(), term()) -> rebar_state:t().
  241. set_global_flag(State, Options, Flag) ->
  242. Value = case proplists:get_bool(Flag, Options) of
  243. true ->
  244. "1";
  245. false ->
  246. "0"
  247. end,
  248. rebar_state:set(State, Flag, Value).
  249. %% @doc options accepted via getopt
  250. -spec global_option_spec_list() -> [{atom(), char(), string(), atom(), string()}, ...].
  251. global_option_spec_list() ->
  252. [
  253. %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
  254. {help, $h, "help", undefined, "Print this help."},
  255. {version, $v, "version", undefined, "Show version information."},
  256. {task, undefined, undefined, string, "Task to run."}
  257. ].
  258. %% @private translate unhandled errors and internal return codes into proper
  259. %% erroneous program exits.
  260. -spec handle_error(term()) -> no_return().
  261. handle_error(rebar_abort) ->
  262. erlang:halt(1);
  263. handle_error({error, rebar_abort}) ->
  264. erlang:halt(1);
  265. handle_error({error, {Module, Reason}}) ->
  266. case code:which(Module) of
  267. non_existing ->
  268. ?CRASHDUMP("~p: ~p~n~p~n~n", [Module, Reason, erlang:get_stacktrace()]),
  269. ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to stacktrace or consult rebar3.crashdump", []),
  270. ?DEBUG("Uncaught error: ~p ~p", [Module, Reason]),
  271. ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []);
  272. _ ->
  273. ?ERROR("~s", [Module:format_error(Reason)])
  274. end,
  275. erlang:halt(1);
  276. handle_error({error, Error}) when is_list(Error) ->
  277. ?ERROR("~s", [Error]),
  278. erlang:halt(1);
  279. handle_error(Error) ->
  280. %% Nothing should percolate up from rebar_core;
  281. %% Dump this error to console
  282. StackTrace = erlang:get_stacktrace(),
  283. ?CRASHDUMP("Error: ~p~n~p~n~n", [Error, StackTrace]),
  284. ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace or consult rebar3.crashdump", []),
  285. ?DEBUG("Uncaught error: ~p", [Error]),
  286. case StackTrace of
  287. [] -> ok;
  288. Trace ->
  289. ?DEBUG("Stack trace to the error location:~n~p", [Trace])
  290. end,
  291. ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []),
  292. erlang:halt(1).
  293. %% @private Boot Erlang dependencies; problem is that escripts don't auto-boot
  294. %% stuff the way releases do and we have to do it by hand.
  295. %% This also lets us detect and show nicer errors when a critical lib is
  296. %% not supported
  297. -spec start_and_load_apps(command_line|api) -> term().
  298. start_and_load_apps(Caller) ->
  299. _ = application:load(rebar),
  300. %% Make sure crypto is running
  301. ensure_running(crypto, Caller),
  302. ensure_running(asn1, Caller),
  303. ensure_running(public_key, Caller),
  304. ensure_running(ssl, Caller),
  305. inets:start(),
  306. inets:start(httpc, [{profile, rebar}]).
  307. %% @doc Make sure a required app is running, or display an error message
  308. %% and abort if there's a problem.
  309. -spec ensure_running(atom(), command_line|api) -> ok | no_return().
  310. ensure_running(App, Caller) ->
  311. case application:start(App) of
  312. ok -> ok;
  313. {error, {already_started, App}} -> ok;
  314. {error, Reason} ->
  315. %% These errors keep rebar3's own configuration to be loaded,
  316. %% which disables the log level and causes a failure without
  317. %% showing the error message. Bypass this entirely by overriding
  318. %% the default value (which allows logging to take place)
  319. %% and shut things down manually.
  320. Log = ec_cmd_log:new(warn, Caller),
  321. ec_cmd_log:error(Log, "Rebar dependency ~p could not be loaded "
  322. "for reason ~p~n", [App, Reason]),
  323. throw(rebar_abort)
  324. end.
  325. -spec state_from_global_config([term()], file:filename()) -> rebar_state:t().
  326. state_from_global_config(Config, GlobalConfigFile) ->
  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.