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

363 lines
13 KiB

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