您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

523 行
21 KiB

Support old-style shell for rebar3 shell This is quite the hack. This requires to detect the current shell running; if it's the new shell, business as usual. However, if it's the old shell, we have to find a way to take over it and drive IO. This requires a few steps because: - the old shell does not let you be supervised intelligently (it uses supervisor_bridge, so killing the child is not a supported operation from the supervisor) - the old shell ignores all trappable exit signals except those coming from the Port in charge of stdio ({fd, 0, 1}) - the old shell shuts down on all exit signals from the stdio Port except for badsig, and replicates the shutdown reason otherwise - An escript does not tolerate the `user` process dying (old shell) for any non-normal reason without also taking the whole escript down - Booting in an escript has an implicit 'noshell' argument interpreted by the old shell as a way to boot the stdio Port with only stdout taken care of Because of all these points, we have to kill the old `user` process by sending it a message pretending to be the Stdio port dying of reason `normal`, which lets it die without triggering the ire of its supervision tree and keeping the escript alive. This, in turn, kills the old stdio port since its parent (user.erl) has died. Then we have to boot our copy of user.erl (rebar_user.erl) which conveniently ignores the possibility of running the stdio port on stdout only -- always using stdin *and* stdout, giving us a bona fide old-style shell. A known issue introduced is that running r3:do(ct) seems to then kill the shell, and r3:do(dialyzer) appears to have an odd failure, but otherwise most other commands appear to work fine.
9 年前
support for hex v2, multiple repository fetching, private organizations (#1884) * update to hex_core for hex-v2 repo support (#1865) * update to hex_core for hex-v2 repo support This patch adds only single repo hex-v2 support through hex_core. Packages no longer filtered out by buildtool metadata and the package index is updated per-package instead of fetched as one large ets dump. * tell travis to also build hex_core branch * support list of repos for hex packages (#1866) * support list of repos for hex packages repos are defined under the hex key in rebar configs. They can be defined at the top level of a project or globally, but not in profiles and the repos configured in dependencies are also ignored. Searching for packages involves first checking for a match in the local repo index cache, in the order repos are defined. If not found each repo is checked through the hex api for any known versions of the package and the first repo with a version that fits the constraint is used. * add {repos, replace, []} for overriding the global & default repos * add hex auth handling for repos (#1874) auth token are kept in a hex.config file that is modified by the rebar3 hex plugin. Repo names that have a : separating a parent and child are considered organizations. The parent repo's auth will be included with the child. So an organization named hexpm:rebar3_test will include any hexpm auth tokens found in the rebar3_test organization's configuration. * move packages to top level of of hexpm cache dir (#1876) * move packages to top level of of hexpm cache dir * append organization name to parent's repo_url when parsing repos * only eval config scripts and apply overrides once per app (#1879) * only eval config scripts and apply overrides once per app * move new resource behaviour to rebar_resource_v2 and keep v1 * cleanup use of rebar_resource module and unused functions * cleanup error messages and unused code * when discovering apps support mix packages as unbuilt apps (#1882) * use hex_core tarball unpacking support in pkg resource (#1883) * use hex_core tarball unpacking support in pkg resource * ignore etag if package doesn't exist and delete if checksum fails * add back tests for bad package checksums * improve bad registry checksum error message
6 年前
Support old-style shell for rebar3 shell This is quite the hack. This requires to detect the current shell running; if it's the new shell, business as usual. However, if it's the old shell, we have to find a way to take over it and drive IO. This requires a few steps because: - the old shell does not let you be supervised intelligently (it uses supervisor_bridge, so killing the child is not a supported operation from the supervisor) - the old shell ignores all trappable exit signals except those coming from the Port in charge of stdio ({fd, 0, 1}) - the old shell shuts down on all exit signals from the stdio Port except for badsig, and replicates the shutdown reason otherwise - An escript does not tolerate the `user` process dying (old shell) for any non-normal reason without also taking the whole escript down - Booting in an escript has an implicit 'noshell' argument interpreted by the old shell as a way to boot the stdio Port with only stdout taken care of Because of all these points, we have to kill the old `user` process by sending it a message pretending to be the Stdio port dying of reason `normal`, which lets it die without triggering the ire of its supervision tree and keeping the escript alive. This, in turn, kills the old stdio port since its parent (user.erl) has died. Then we have to boot our copy of user.erl (rebar_user.erl) which conveniently ignores the possibility of running the stdio port on stdout only -- always using stdin *and* stdout, giving us a bona fide old-style shell. A known issue introduced is that running r3:do(ct) seems to then kill the shell, and r3:do(dialyzer) appears to have an odd failure, but otherwise most other commands appear to work fine.
9 年前
Support old-style shell for rebar3 shell This is quite the hack. This requires to detect the current shell running; if it's the new shell, business as usual. However, if it's the old shell, we have to find a way to take over it and drive IO. This requires a few steps because: - the old shell does not let you be supervised intelligently (it uses supervisor_bridge, so killing the child is not a supported operation from the supervisor) - the old shell ignores all trappable exit signals except those coming from the Port in charge of stdio ({fd, 0, 1}) - the old shell shuts down on all exit signals from the stdio Port except for badsig, and replicates the shutdown reason otherwise - An escript does not tolerate the `user` process dying (old shell) for any non-normal reason without also taking the whole escript down - Booting in an escript has an implicit 'noshell' argument interpreted by the old shell as a way to boot the stdio Port with only stdout taken care of Because of all these points, we have to kill the old `user` process by sending it a message pretending to be the Stdio port dying of reason `normal`, which lets it die without triggering the ire of its supervision tree and keeping the escript alive. This, in turn, kills the old stdio port since its parent (user.erl) has died. Then we have to boot our copy of user.erl (rebar_user.erl) which conveniently ignores the possibility of running the stdio port on stdout only -- always using stdin *and* stdout, giving us a bona fide old-style shell. A known issue introduced is that running r3:do(ct) seems to then kill the shell, and r3:do(dialyzer) appears to have an odd failure, but otherwise most other commands appear to work fine.
9 年前
Support old-style shell for rebar3 shell This is quite the hack. This requires to detect the current shell running; if it's the new shell, business as usual. However, if it's the old shell, we have to find a way to take over it and drive IO. This requires a few steps because: - the old shell does not let you be supervised intelligently (it uses supervisor_bridge, so killing the child is not a supported operation from the supervisor) - the old shell ignores all trappable exit signals except those coming from the Port in charge of stdio ({fd, 0, 1}) - the old shell shuts down on all exit signals from the stdio Port except for badsig, and replicates the shutdown reason otherwise - An escript does not tolerate the `user` process dying (old shell) for any non-normal reason without also taking the whole escript down - Booting in an escript has an implicit 'noshell' argument interpreted by the old shell as a way to boot the stdio Port with only stdout taken care of Because of all these points, we have to kill the old `user` process by sending it a message pretending to be the Stdio port dying of reason `normal`, which lets it die without triggering the ire of its supervision tree and keeping the escript alive. This, in turn, kills the old stdio port since its parent (user.erl) has died. Then we have to boot our copy of user.erl (rebar_user.erl) which conveniently ignores the possibility of running the stdio port on stdout only -- always using stdin *and* stdout, giving us a bona fide old-style shell. A known issue introduced is that running r3:do(ct) seems to then kill the shell, and r3:do(dialyzer) appears to have an odd failure, but otherwise most other commands appear to work fine.
9 年前
  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) 2011 Trifork
  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_prv_shell).
  28. -author("Kresten Krab Thorup <krab@trifork.com>").
  29. -author("Fred Hebert <mononcqc@ferd.ca>").
  30. -behaviour(provider).
  31. -export([init/1,
  32. do/1,
  33. format_error/1]).
  34. -include("rebar.hrl").
  35. -define(PROVIDER, shell).
  36. -define(DEPS, [compile]).
  37. -dialyzer({nowarn_function, rewrite_leaders/2}).
  38. %% ===================================================================
  39. %% Public API
  40. %% ===================================================================
  41. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  42. init(State) ->
  43. State1 = rebar_state:add_provider(
  44. State,
  45. providers:create([
  46. {name, ?PROVIDER},
  47. {module, ?MODULE},
  48. {bare, true},
  49. {deps, ?DEPS},
  50. {example, "rebar3 shell"},
  51. {short_desc, "Run shell with project apps and deps in path."},
  52. {desc, info()},
  53. {opts, [{config, undefined, "config", string,
  54. "Path to the config file to use. Defaults to "
  55. "{shell, [{config, File}]} and then the relx "
  56. "sys.config file if not specified."},
  57. {name, undefined, "name", atom,
  58. "Gives a long name to the node."},
  59. {sname, undefined, "sname", atom,
  60. "Gives a short name to the node."},
  61. {setcookie, undefined, "setcookie", atom,
  62. "Sets the cookie if the node is distributed."},
  63. {script_file, undefined, "script", string,
  64. "Path to an escript file to run before "
  65. "starting the project apps. Defaults to "
  66. "rebar.config {shell, [{script_file, File}]} "
  67. "if not specified."},
  68. {apps, undefined, "apps", string,
  69. "A list of apps to boot before starting the "
  70. "shell. (E.g. --apps app1,app2,app3) Defaults "
  71. "to rebar.config {shell, [{apps, Apps}]} or "
  72. "relx apps if not specified."},
  73. {start_clean, undefined, "start-clean", boolean,
  74. "Cancel any applications in the 'apps' list "
  75. "or release."},
  76. {user_drv_args, undefined, "user_drv_args", string,
  77. "Arguments passed to user_drv start function for "
  78. "creating custom shells."}]}
  79. ])
  80. ),
  81. {ok, State1}.
  82. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  83. do(Config) ->
  84. shell(Config),
  85. {ok, Config}.
  86. -spec format_error(any()) -> iolist().
  87. format_error(Reason) ->
  88. io_lib:format("~p", [Reason]).
  89. %% NOTE:
  90. %% this is an attempt to replicate `erl -pa ./ebin -pa deps/*/ebin`. it is
  91. %% mostly successful but does stop and then restart the user io system to get
  92. %% around issues with rebar being an escript and starting in `noshell` mode.
  93. %% it also lacks the ctrl-c interrupt handler that `erl` features. ctrl-c will
  94. %% immediately kill the script. ctrl-g, however, works fine
  95. shell(State) ->
  96. setup_name(State),
  97. setup_paths(State),
  98. ShellArgs = debug_get_value(shell_args, rebar_state:get(State, shell, []), undefined,
  99. "Found user_drv args from command line option."),
  100. setup_shell(ShellArgs),
  101. maybe_run_script(State),
  102. %% apps must be started after the change in shell because otherwise
  103. %% their application masters never gets the new group leader (held in
  104. %% their internal state)
  105. maybe_boot_apps(State),
  106. simulate_proc_lib(),
  107. true = register(rebar_agent, self()),
  108. {ok, GenState} = rebar_agent:init(State),
  109. %% Hack to fool the init process into thinking we have stopped and the normal
  110. %% node start process can go on. Without it, init:get_status() always return
  111. %% '{starting, started}' instead of '{started, started}'
  112. init ! {'EXIT', self(), normal},
  113. gen_server:enter_loop(rebar_agent, [], GenState, {local, rebar_agent}, hibernate).
  114. info() ->
  115. "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n".
  116. setup_shell(ShellArgs) ->
  117. LoggerState = maybe_remove_logger(),
  118. OldUser = kill_old_user(),
  119. %% Test for support here
  120. NewUser = try erlang:open_port({spawn,"tty_sl -c -e"}, []) of
  121. Port when is_port(Port) ->
  122. true = port_close(Port),
  123. setup_new_shell(ShellArgs)
  124. catch
  125. error:_ ->
  126. setup_old_shell()
  127. end,
  128. rewrite_leaders(OldUser, NewUser),
  129. maybe_reset_logger(LoggerState).
  130. %% @private starting with OTP-21.2.3, there's an oddity where the logger
  131. %% likely tries to handle system logs while we take down the TTY, which
  132. %% ends up hanging the default logger. This function (along with
  133. %% `maybe_reset_logger/1') removes and re-adds the default logger before and
  134. %% after the TTY subsystem is taken offline, which prevents such hanging.
  135. maybe_remove_logger() ->
  136. case erlang:function_exported(logger, module_info, 0) of
  137. false ->
  138. ignore;
  139. true ->
  140. {ok, Cfg} = logger:get_handler_config(default),
  141. logger:remove_handler(default),
  142. {restart, Cfg}
  143. end.
  144. maybe_reset_logger(ignore) ->
  145. ok;
  146. maybe_reset_logger({restart, #{module := Mod, config := Cfg}}) ->
  147. logger:add_handler(default, Mod, Cfg).
  148. kill_old_user() ->
  149. OldUser = whereis(user),
  150. %% terminate the current user's port, in a way that makes it shut down,
  151. %% but without taking down the supervision tree so that the escript doesn't
  152. %% fully die
  153. [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],
  154. user ! {'EXIT', P, normal}, % pretend the port died, then the port can die!
  155. exit(P, kill),
  156. wait_for_port_death(1000, P),
  157. OldUser.
  158. wait_for_port_death(N, _) when N < 0 ->
  159. %% This risks displaying a warning!
  160. whatever;
  161. wait_for_port_death(N, P) ->
  162. case erlang:port_info(P) of
  163. undefined ->
  164. ok;
  165. _ ->
  166. timer:sleep(10),
  167. wait_for_port_death(N-10, P)
  168. end.
  169. setup_new_shell(ShellArgs) ->
  170. %% terminate the current user supervision structure, if any
  171. _ = supervisor:terminate_child(kernel_sup, user),
  172. %% start a new shell (this also starts a new user under the correct group)
  173. case ShellArgs of
  174. undefined ->
  175. _ = user_drv:start();
  176. _ ->
  177. _ = user_drv:start(ShellArgs)
  178. end,
  179. %% wait until user_drv and user have been registered (max 3 seconds)
  180. ok = wait_until_user_started(3000),
  181. whereis(user).
  182. setup_old_shell() ->
  183. %% scan all processes for any with references to the old user and save them to
  184. %% update later
  185. NewUser = rebar_user:start(), % hikack IO stuff with fake user
  186. NewUser = whereis(user),
  187. NewUser.
  188. rewrite_leaders(OldUser, NewUser) ->
  189. %% set any process that had a reference to the old user's group leader to the
  190. %% new user process. Catch the race condition when the Pid exited after the
  191. %% liveness check.
  192. _ = [catch erlang:group_leader(NewUser, Pid)
  193. || Pid <- erlang:processes(),
  194. proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
  195. is_process_alive(Pid)],
  196. %% Application masters have the same problem, but they hold the old group
  197. %% leader in their state and hold on to it. Re-point the processes whose
  198. %% leaders are application masters. This can mess up a few things around
  199. %% shutdown time, but is nicer than the current lock-up.
  200. OldMasters = [Pid
  201. || Pid <- erlang:processes(),
  202. Pid < NewUser, % only change old masters
  203. {_,Dict} <- [erlang:process_info(Pid, dictionary)],
  204. {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
  205. _ = [catch erlang:group_leader(NewUser, Pid)
  206. || Pid <- erlang:processes(),
  207. lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
  208. OldMasters)],
  209. try
  210. case erlang:function_exported(logger, module_info, 0) of
  211. false ->
  212. %% Old style logger had a lock-up issue and other problems related
  213. %% to group leader handling.
  214. %% enable error_logger's tty output
  215. error_logger:swap_handler(tty),
  216. %% disable the simple error_logger (which may have been added
  217. %% multiple times). removes at most the error_logger added by
  218. %% init and the error_logger added by the tty handler
  219. remove_error_handler(3),
  220. %% reset the tty handler once more for remote shells
  221. error_logger:swap_handler(tty);
  222. true ->
  223. %% This is no longer a problem with the logger interface
  224. ok
  225. end
  226. catch
  227. ?WITH_STACKTRACE(E,R,S) % may fail with custom loggers
  228. ?DEBUG("Logger changes failed for ~p:~p (~p)", [E,R,S]),
  229. hope_for_best
  230. end.
  231. setup_paths(State) ->
  232. %% Add deps to path
  233. code:add_pathsa(rebar_state:code_paths(State, all_deps)),
  234. %% add project app test paths
  235. ok = add_test_paths(State).
  236. maybe_run_script(State) ->
  237. case first_value([fun find_script_option/1,
  238. fun find_script_rebar/1], State) of
  239. no_value ->
  240. ?DEBUG("No script_file specified.", []),
  241. ok;
  242. "none" ->
  243. ?DEBUG("Shell script execution skipped (--script none).", []),
  244. ok;
  245. RelFile ->
  246. File = filename:absname(RelFile),
  247. try run_script_file(File)
  248. catch
  249. ?WITH_STACKTRACE(C,E,S)
  250. ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p",
  251. [File, C, E, S])
  252. end
  253. end.
  254. -spec find_script_option(rebar_state:t()) -> no_value | list().
  255. find_script_option(State) ->
  256. {Opts, _} = rebar_state:command_parsed_args(State),
  257. debug_get_value(script_file, Opts, no_value,
  258. "Found script file from command line option.").
  259. -spec find_script_rebar(rebar_state:t()) -> no_value | list().
  260. find_script_rebar(State) ->
  261. Config = rebar_state:get(State, shell, []),
  262. %% Either a string, or undefined
  263. debug_get_value(script_file, Config, no_value,
  264. "Found script file from rebar config file.").
  265. run_script_file(File) ->
  266. ?DEBUG("Extracting escript from ~p", [File]),
  267. {ok, Script} = escript:extract(File, [compile_source]),
  268. Beam = proplists:get_value(source, Script),
  269. Mod = proplists:get_value(module, beam_lib:info(Beam)),
  270. ?DEBUG("Compiled escript as ~p", [Mod]),
  271. FakeFile = "/fake_path/" ++ atom_to_list(Mod),
  272. {module, Mod} = code:load_binary(Mod, FakeFile, Beam),
  273. ?DEBUG("Evaling ~p:main([]).", [Mod]),
  274. Result = Mod:main([]),
  275. ?DEBUG("Result: ~p", [Result]),
  276. Result.
  277. maybe_boot_apps(State) ->
  278. case find_apps_to_boot(State) of
  279. undefined ->
  280. %% try to read in sys.config file
  281. ok = reread_config([], State);
  282. Apps ->
  283. %% load apps, then check config, then boot them.
  284. load_apps(Apps),
  285. ok = reread_config(Apps, State),
  286. boot_apps(Apps)
  287. end.
  288. simulate_proc_lib() ->
  289. FakeParent = spawn_link(fun() -> timer:sleep(infinity) end),
  290. put('$ancestors', [FakeParent]),
  291. put('$initial_call', {rebar_agent, init, 1}).
  292. setup_name(State) ->
  293. {Long, Short, Opts} = rebar_dist_utils:find_options(State),
  294. rebar_dist_utils:either(Long, Short, Opts).
  295. find_apps_to_boot(State) ->
  296. %% Try the shell_apps option
  297. case first_value([fun find_apps_option/1,
  298. fun find_apps_rebar/1,
  299. fun find_apps_relx/1], State) of
  300. no_value ->
  301. undefined;
  302. Apps ->
  303. Apps
  304. end.
  305. -spec find_apps_option(rebar_state:t()) -> no_value | [atom()].
  306. find_apps_option(State) ->
  307. {Opts, _} = rebar_state:command_parsed_args(State),
  308. case debug_get_value(apps, Opts, no_value,
  309. "Found shell apps from command line option.") of
  310. no_value ->
  311. case debug_get_value(start_clean, Opts, false,
  312. "Found start-clean argument to disable apps") of
  313. false -> no_value;
  314. true -> []
  315. end;
  316. AppsStr ->
  317. [ list_to_atom(AppStr)
  318. || AppStr <- rebar_string:lexemes(AppsStr, " ,:") ]
  319. end.
  320. -spec find_apps_rebar(rebar_state:t()) -> no_value | list().
  321. find_apps_rebar(State) ->
  322. ShellOpts = rebar_state:get(State, shell, []),
  323. debug_get_value(apps, ShellOpts, no_value,
  324. "Found shell opts from command line option.").
  325. -spec find_apps_relx(rebar_state:t()) -> no_value | list().
  326. find_apps_relx(State) ->
  327. case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
  328. {_, _, Apps} ->
  329. ?DEBUG("Found shell apps from relx.", []),
  330. Apps;
  331. {_, _, Apps, _} ->
  332. ?DEBUG("Found shell apps from relx.", []),
  333. Apps;
  334. false ->
  335. no_value
  336. end.
  337. load_apps(Apps) ->
  338. [case application:load(App) of
  339. ok ->
  340. {ok, Ks} = application:get_all_key(App),
  341. load_apps(proplists:get_value(applications, Ks));
  342. _ ->
  343. error % will be caught when starting the app
  344. end || App <- normalize_load_apps(Apps),
  345. not lists:keymember(App, 1, application:loaded_applications())],
  346. ok.
  347. reread_config(AppsToStart, State) ->
  348. case find_config(State) of
  349. no_config ->
  350. ok;
  351. ConfigList ->
  352. %% This allows people who use applications that are also
  353. %% depended on by rebar3 or its plugins to change their
  354. %% configuration at runtime based on the configuration files.
  355. %%
  356. %% To do this, we stop apps that are already started before
  357. %% reloading their configuration.
  358. %%
  359. %% We make an exception for apps that:
  360. %% - are not already running
  361. %% - would not be restarted (and hence would break some
  362. %% compatibility with rebar3)
  363. %% - are not in the config files and would see no config
  364. %% changes
  365. %% - are not in a blacklist, where changing their config
  366. %% would be risky to the shell or the rebar3 agent
  367. %% functionality (i.e. changing inets may break proxy
  368. %% settings, stopping `kernel' would break everything)
  369. Running = [App || {App, _, _} <- application:which_applications()],
  370. BlackList = [inets, stdlib, kernel, rebar],
  371. _ = [application:stop(App)
  372. || Config <- ConfigList,
  373. {App, _} <- Config,
  374. lists:member(App, Running),
  375. lists:member(App, AppsToStart),
  376. not lists:member(App, BlackList)],
  377. _ = rebar_utils:reread_config(ConfigList, [update_logger]),
  378. ok
  379. end.
  380. boot_apps(Apps) ->
  381. ?WARN("The rebar3 shell is a development tool; to deploy "
  382. "applications in production, consider using releases "
  383. "(http://www.rebar3.org/docs/releases)", []),
  384. Normalized = normalize_boot_apps(Apps),
  385. Res = [application:ensure_all_started(App) || App <- Normalized],
  386. _ = [?INFO("Booted ~p", [App])
  387. || {ok, Booted} <- Res,
  388. App <- Booted],
  389. _ = [?ERROR("Failed to boot ~p for reason ~p", [App, Reason])
  390. || {error, {App, Reason}} <- Res],
  391. ok.
  392. normalize_load_apps([]) -> [];
  393. normalize_load_apps([{_App, none} | T]) -> normalize_load_apps(T);
  394. normalize_load_apps([{App, _} | T]) -> [App | normalize_load_apps(T)];
  395. normalize_load_apps([{App, _Vsn, load} | T]) -> [App | normalize_load_apps(T)];
  396. normalize_load_apps([{_App, _Vsn, none} | T]) -> normalize_load_apps(T);
  397. normalize_load_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) ->
  398. [App | normalize_load_apps(T)];
  399. normalize_load_apps([App | T]) when is_atom(App) -> [App | normalize_load_apps(T)].
  400. normalize_boot_apps([]) -> [];
  401. normalize_boot_apps([{_App, load} | T]) -> normalize_boot_apps(T);
  402. normalize_boot_apps([{_App, _Vsn, load} | T]) -> normalize_boot_apps(T);
  403. normalize_boot_apps([{_App, none} | T]) -> normalize_boot_apps(T);
  404. normalize_boot_apps([{_App, _Vsn, none} | T]) -> normalize_boot_apps(T);
  405. normalize_boot_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) ->
  406. [App | normalize_boot_apps(T)];
  407. normalize_boot_apps([{App, _Vsn} | T]) -> [App | normalize_boot_apps(T)];
  408. normalize_boot_apps([App | T]) when is_atom(App) -> [App | normalize_boot_apps(T)].
  409. remove_error_handler(0) ->
  410. ?WARN("Unable to remove simple error_logger handler", []);
  411. remove_error_handler(N) ->
  412. case gen_event:delete_handler(error_logger, error_logger, []) of
  413. {error, module_not_found} -> ok;
  414. {error_logger, _} -> remove_error_handler(N-1)
  415. end.
  416. %% Timeout is a period to wait before giving up
  417. wait_until_user_started(0) ->
  418. ?ABORT("Timeout exceeded waiting for `user` to register itself", []),
  419. erlang:error(timeout);
  420. wait_until_user_started(Timeout) ->
  421. case whereis(user) of
  422. %% if user is not yet registered wait a tenth of a second and try again
  423. undefined -> timer:sleep(100), wait_until_user_started(Timeout - 100);
  424. _ -> ok
  425. end.
  426. add_test_paths(State) ->
  427. _ = [begin
  428. AppDir = rebar_app_info:out_dir(App),
  429. %% ignore errors resulting from non-existent directories
  430. _ = code:add_path(filename:join([AppDir, "test"]))
  431. end || App <- rebar_state:project_apps(State)],
  432. _ = code:add_path(filename:join([rebar_dir:base_dir(State), "test"])),
  433. ok.
  434. % First try the --config flag, then try the relx sys_config
  435. -spec find_config(rebar_state:t()) -> [[tuple()]] | no_config.
  436. find_config(State) ->
  437. case first_value([fun find_config_option/1,
  438. fun find_config_rebar/1,
  439. fun find_config_relx/1], State) of
  440. no_value ->
  441. no_config;
  442. Filename when is_list(Filename) ->
  443. rebar_file_utils:consult_config(State, Filename)
  444. end.
  445. -spec first_value([Fun], State) -> no_value | Value when
  446. Value :: any(),
  447. State :: rebar_state:t(),
  448. Fun :: fun ((State) -> no_value | Value).
  449. first_value([], _) -> no_value;
  450. first_value([Fun | Rest], State) ->
  451. case Fun(State) of
  452. no_value ->
  453. first_value(Rest, State);
  454. Value ->
  455. Value
  456. end.
  457. debug_get_value(Key, List, Default, Description) ->
  458. case proplists:get_value(Key, List, Default) of
  459. Default -> Default;
  460. Value ->
  461. ?DEBUG(Description, []),
  462. Value
  463. end.
  464. -spec find_config_option(rebar_state:t()) -> Filename::list() | no_value.
  465. find_config_option(State) ->
  466. {Opts, _} = rebar_state:command_parsed_args(State),
  467. debug_get_value(config, Opts, no_value,
  468. "Found config from command line option.").
  469. -spec find_config_rebar(rebar_state:t()) -> [tuple()] | no_value.
  470. find_config_rebar(State) ->
  471. debug_get_value(config, rebar_state:get(State, shell, []), no_value,
  472. "Found config from rebar config file.").
  473. -spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value.
  474. find_config_relx(State) ->
  475. debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value,
  476. "Found config from relx.").