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.

484 lines
19 KiB

пре 10 година
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 година
пре 10 година
пре 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 година
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 година
пре 10 година
пре 10 година
пре 10 година
  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. %% ===================================================================
  38. %% Public API
  39. %% ===================================================================
  40. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  41. init(State) ->
  42. State1 = rebar_state:add_provider(
  43. State,
  44. providers:create([
  45. {name, ?PROVIDER},
  46. {module, ?MODULE},
  47. {bare, true},
  48. {deps, ?DEPS},
  49. {example, "rebar3 shell"},
  50. {short_desc, "Run shell with project apps and deps in path."},
  51. {desc, info()},
  52. {opts, [{config, undefined, "config", string,
  53. "Path to the config file to use. Defaults to "
  54. "{shell, [{config, File}]} and then the relx "
  55. "sys.config file if not specified."},
  56. {name, undefined, "name", atom,
  57. "Gives a long name to the node."},
  58. {sname, undefined, "sname", atom,
  59. "Gives a short name to the node."},
  60. {setcookie, undefined, "setcookie", atom,
  61. "Sets the cookie if the node is distributed."},
  62. {script_file, undefined, "script", string,
  63. "Path to an escript file to run before "
  64. "starting the project apps. Defaults to "
  65. "rebar.config {shell, [{script_file, File}]} "
  66. "if not specified."},
  67. {apps, undefined, "apps", string,
  68. "A list of apps to boot before starting the "
  69. "shell. (E.g. --apps app1,app2,app3) Defaults "
  70. "to rebar.config {shell, [{apps, Apps}]} or "
  71. "relx apps if not specified."},
  72. {user_drv_args, undefined, "user_drv_args", string,
  73. "Arguments passed to user_drv start function for "
  74. "creating custom shells."}]}
  75. ])
  76. ),
  77. {ok, State1}.
  78. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  79. do(Config) ->
  80. shell(Config),
  81. {ok, Config}.
  82. -spec format_error(any()) -> iolist().
  83. format_error(Reason) ->
  84. io_lib:format("~p", [Reason]).
  85. %% NOTE:
  86. %% this is an attempt to replicate `erl -pa ./ebin -pa deps/*/ebin`. it is
  87. %% mostly successful but does stop and then restart the user io system to get
  88. %% around issues with rebar being an escript and starting in `noshell` mode.
  89. %% it also lacks the ctrl-c interrupt handler that `erl` features. ctrl-c will
  90. %% immediately kill the script. ctrl-g, however, works fine
  91. shell(State) ->
  92. setup_name(State),
  93. setup_paths(State),
  94. ShellArgs = debug_get_value(shell_args, rebar_state:get(State, shell, []), undefined,
  95. "Found user_drv args from command line option."),
  96. setup_shell(ShellArgs),
  97. maybe_run_script(State),
  98. %% apps must be started after the change in shell because otherwise
  99. %% their application masters never gets the new group leader (held in
  100. %% their internal state)
  101. maybe_boot_apps(State),
  102. simulate_proc_lib(),
  103. true = register(rebar_agent, self()),
  104. {ok, GenState} = rebar_agent:init(State),
  105. %% Hack to fool the init process into thinking we have stopped and the normal
  106. %% node start process can go on. Without it, init:get_status() always return
  107. %% '{starting, started}' instead of '{started, started}'
  108. init ! {'EXIT', self(), normal},
  109. gen_server:enter_loop(rebar_agent, [], GenState, {local, rebar_agent}, hibernate).
  110. info() ->
  111. "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n".
  112. setup_shell(ShellArgs) ->
  113. OldUser = kill_old_user(),
  114. %% Test for support here
  115. NewUser = try erlang:open_port({spawn,"tty_sl -c -e"}, []) of
  116. Port when is_port(Port) ->
  117. true = port_close(Port),
  118. setup_new_shell(ShellArgs)
  119. catch
  120. error:_ ->
  121. setup_old_shell()
  122. end,
  123. rewrite_leaders(OldUser, NewUser).
  124. kill_old_user() ->
  125. OldUser = whereis(user),
  126. %% terminate the current user's port, in a way that makes it shut down,
  127. %% but without taking down the supervision tree so that the escript doesn't
  128. %% fully die
  129. [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],
  130. user ! {'EXIT', P, normal}, % pretend the port died, then the port can die!
  131. exit(P, kill),
  132. wait_for_port_death(1000, P),
  133. OldUser.
  134. wait_for_port_death(N, _) when N < 0 ->
  135. %% This risks displaying a warning!
  136. whatever;
  137. wait_for_port_death(N, P) ->
  138. case erlang:port_info(P) of
  139. undefined ->
  140. ok;
  141. _ ->
  142. timer:sleep(10),
  143. wait_for_port_death(N-10, P)
  144. end.
  145. setup_new_shell(ShellArgs) ->
  146. %% terminate the current user supervision structure, if any
  147. _ = supervisor:terminate_child(kernel_sup, user),
  148. %% start a new shell (this also starts a new user under the correct group)
  149. case ShellArgs of
  150. undefined ->
  151. _ = user_drv:start();
  152. _ ->
  153. _ = user_drv:start(ShellArgs)
  154. end,
  155. %% wait until user_drv and user have been registered (max 3 seconds)
  156. ok = wait_until_user_started(3000),
  157. whereis(user).
  158. setup_old_shell() ->
  159. %% scan all processes for any with references to the old user and save them to
  160. %% update later
  161. NewUser = rebar_user:start(), % hikack IO stuff with fake user
  162. NewUser = whereis(user),
  163. NewUser.
  164. rewrite_leaders(OldUser, NewUser) ->
  165. %% set any process that had a reference to the old user's group leader to the
  166. %% new user process. Catch the race condition when the Pid exited after the
  167. %% liveness check.
  168. _ = [catch erlang:group_leader(NewUser, Pid)
  169. || Pid <- erlang:processes(),
  170. proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
  171. is_process_alive(Pid)],
  172. %% Application masters have the same problem, but they hold the old group
  173. %% leader in their state and hold on to it. Re-point the processes whose
  174. %% leaders are application masters. This can mess up a few things around
  175. %% shutdown time, but is nicer than the current lock-up.
  176. OldMasters = [Pid
  177. || Pid <- erlang:processes(),
  178. Pid < NewUser, % only change old masters
  179. {_,Dict} <- [erlang:process_info(Pid, dictionary)],
  180. {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
  181. _ = [catch erlang:group_leader(NewUser, Pid)
  182. || Pid <- erlang:processes(),
  183. lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
  184. OldMasters)],
  185. try
  186. %% enable error_logger's tty output
  187. error_logger:swap_handler(tty),
  188. %% disable the simple error_logger (which may have been added multiple
  189. %% times). removes at most the error_logger added by init and the
  190. %% error_logger added by the tty handler
  191. remove_error_handler(3),
  192. %% reset the tty handler once more for remote shells
  193. error_logger:swap_handler(tty)
  194. catch
  195. E:R -> % may fail with custom loggers
  196. ?DEBUG("Logger changes failed for ~p:~p (~p)", [E,R,erlang:get_stacktrace()]),
  197. hope_for_best
  198. end.
  199. setup_paths(State) ->
  200. %% Add deps to path
  201. code:add_pathsa(rebar_state:code_paths(State, all_deps)),
  202. %% add project app test paths
  203. ok = add_test_paths(State).
  204. maybe_run_script(State) ->
  205. case first_value([fun find_script_option/1,
  206. fun find_script_rebar/1], State) of
  207. no_value ->
  208. ?DEBUG("No script_file specified.", []),
  209. ok;
  210. "none" ->
  211. ?DEBUG("Shell script execution skipped (--script none).", []),
  212. ok;
  213. RelFile ->
  214. File = filename:absname(RelFile),
  215. try run_script_file(File)
  216. catch
  217. C:E ->
  218. ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p",
  219. [File, C, E, erlang:get_stacktrace()])
  220. end
  221. end.
  222. -spec find_script_option(rebar_state:t()) -> no_value | list().
  223. find_script_option(State) ->
  224. {Opts, _} = rebar_state:command_parsed_args(State),
  225. debug_get_value(script_file, Opts, no_value,
  226. "Found script file from command line option.").
  227. -spec find_script_rebar(rebar_state:t()) -> no_value | list().
  228. find_script_rebar(State) ->
  229. Config = rebar_state:get(State, shell, []),
  230. %% Either a string, or undefined
  231. debug_get_value(script_file, Config, no_value,
  232. "Found script file from rebar config file.").
  233. run_script_file(File) ->
  234. ?DEBUG("Extracting escript from ~p", [File]),
  235. {ok, Script} = escript:extract(File, [compile_source]),
  236. Beam = proplists:get_value(source, Script),
  237. Mod = proplists:get_value(module, beam_lib:info(Beam)),
  238. ?DEBUG("Compiled escript as ~p", [Mod]),
  239. FakeFile = "/fake_path/" ++ atom_to_list(Mod),
  240. {module, Mod} = code:load_binary(Mod, FakeFile, Beam),
  241. ?DEBUG("Evaling ~p:main([]).", [Mod]),
  242. Result = Mod:main([]),
  243. ?DEBUG("Result: ~p", [Result]),
  244. Result.
  245. maybe_boot_apps(State) ->
  246. case find_apps_to_boot(State) of
  247. undefined ->
  248. %% try to read in sys.config file
  249. ok = reread_config([], State);
  250. Apps ->
  251. %% load apps, then check config, then boot them.
  252. load_apps(Apps),
  253. ok = reread_config(Apps, State),
  254. boot_apps(Apps)
  255. end.
  256. simulate_proc_lib() ->
  257. FakeParent = spawn_link(fun() -> timer:sleep(infinity) end),
  258. put('$ancestors', [FakeParent]),
  259. put('$initial_call', {rebar_agent, init, 1}).
  260. setup_name(State) ->
  261. {Long, Short, Opts} = rebar_dist_utils:find_options(State),
  262. rebar_dist_utils:either(Long, Short, Opts).
  263. find_apps_to_boot(State) ->
  264. %% Try the shell_apps option
  265. case first_value([fun find_apps_option/1,
  266. fun find_apps_rebar/1,
  267. fun find_apps_relx/1], State) of
  268. no_value ->
  269. undefined;
  270. Apps ->
  271. Apps
  272. end.
  273. -spec find_apps_option(rebar_state:t()) -> no_value | [atom()].
  274. find_apps_option(State) ->
  275. {Opts, _} = rebar_state:command_parsed_args(State),
  276. case debug_get_value(apps, Opts, no_value,
  277. "Found shell apps from command line option.") of
  278. no_value -> no_value;
  279. AppsStr ->
  280. [ list_to_atom(AppStr)
  281. || AppStr <- rebar_string:lexemes(AppsStr, " ,:") ]
  282. end.
  283. -spec find_apps_rebar(rebar_state:t()) -> no_value | list().
  284. find_apps_rebar(State) ->
  285. ShellOpts = rebar_state:get(State, shell, []),
  286. debug_get_value(apps, ShellOpts, no_value,
  287. "Found shell opts from command line option.").
  288. -spec find_apps_relx(rebar_state:t()) -> no_value | list().
  289. find_apps_relx(State) ->
  290. case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
  291. {_, _, Apps} ->
  292. ?DEBUG("Found shell apps from relx.", []),
  293. Apps;
  294. {_, _, Apps, _} ->
  295. ?DEBUG("Found shell apps from relx.", []),
  296. Apps;
  297. false ->
  298. no_value
  299. end.
  300. load_apps(Apps) ->
  301. [case application:load(App) of
  302. ok ->
  303. {ok, Ks} = application:get_all_key(App),
  304. load_apps(proplists:get_value(applications, Ks));
  305. _ ->
  306. error % will be caught when starting the app
  307. end || App <- normalize_load_apps(Apps),
  308. not lists:keymember(App, 1, application:loaded_applications())],
  309. ok.
  310. reread_config(AppsToStart, State) ->
  311. case find_config(State) of
  312. no_config ->
  313. ok;
  314. ConfigList ->
  315. %% This allows people who use applications that are also
  316. %% depended on by rebar3 or its plugins to change their
  317. %% configuration at runtime based on the configuration files.
  318. %%
  319. %% To do this, we stop apps that are already started before
  320. %% reloading their configuration.
  321. %%
  322. %% We make an exception for apps that:
  323. %% - are not already running
  324. %% - would not be restarted (and hence would break some
  325. %% compatibility with rebar3)
  326. %% - are not in the config files and would see no config
  327. %% changes
  328. %% - are not in a blacklist, where changing their config
  329. %% would be risky to the shell or the rebar3 agent
  330. %% functionality (i.e. changing inets may break proxy
  331. %% settings, stopping `kernel' would break everything)
  332. Running = [App || {App, _, _} <- application:which_applications()],
  333. BlackList = [inets, stdlib, kernel, rebar],
  334. _ = [application:stop(App)
  335. || Config <- ConfigList,
  336. {App, _} <- Config,
  337. lists:member(App, Running),
  338. lists:member(App, AppsToStart),
  339. not lists:member(App, BlackList)],
  340. _ = rebar_utils:reread_config(ConfigList),
  341. ok
  342. end.
  343. boot_apps(Apps) ->
  344. ?WARN("The rebar3 shell is a development tool; to deploy "
  345. "applications in production, consider using releases "
  346. "(http://www.rebar3.org/docs/releases)", []),
  347. Normalized = normalize_boot_apps(Apps),
  348. Res = [application:ensure_all_started(App) || App <- Normalized],
  349. _ = [?INFO("Booted ~p", [App])
  350. || {ok, Booted} <- Res,
  351. App <- Booted],
  352. _ = [?ERROR("Failed to boot ~p for reason ~p", [App, Reason])
  353. || {error, {App, Reason}} <- Res],
  354. ok.
  355. normalize_load_apps([]) -> [];
  356. normalize_load_apps([{_App, none} | T]) -> normalize_load_apps(T);
  357. normalize_load_apps([{App, _} | T]) -> [App | normalize_load_apps(T)];
  358. normalize_load_apps([{App, _Vsn, load} | T]) -> [App | normalize_load_apps(T)];
  359. normalize_load_apps([{_App, _Vsn, none} | T]) -> normalize_load_apps(T);
  360. normalize_load_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) ->
  361. [App | normalize_load_apps(T)];
  362. normalize_load_apps([App | T]) when is_atom(App) -> [App | normalize_load_apps(T)].
  363. normalize_boot_apps([]) -> [];
  364. normalize_boot_apps([{_App, load} | T]) -> normalize_boot_apps(T);
  365. normalize_boot_apps([{_App, _Vsn, load} | T]) -> normalize_boot_apps(T);
  366. normalize_boot_apps([{_App, none} | T]) -> normalize_boot_apps(T);
  367. normalize_boot_apps([{_App, _Vsn, none} | T]) -> normalize_boot_apps(T);
  368. normalize_boot_apps([{App, _Vsn, Operator} | T]) when is_atom(Operator) ->
  369. [App | normalize_boot_apps(T)];
  370. normalize_boot_apps([{App, _Vsn} | T]) -> [App | normalize_boot_apps(T)];
  371. normalize_boot_apps([App | T]) when is_atom(App) -> [App | normalize_boot_apps(T)].
  372. remove_error_handler(0) ->
  373. ?WARN("Unable to remove simple error_logger handler", []);
  374. remove_error_handler(N) ->
  375. case gen_event:delete_handler(error_logger, error_logger, []) of
  376. {error, module_not_found} -> ok;
  377. {error_logger, _} -> remove_error_handler(N-1)
  378. end.
  379. %% Timeout is a period to wait before giving up
  380. wait_until_user_started(0) ->
  381. ?ABORT("Timeout exceeded waiting for `user` to register itself", []),
  382. erlang:error(timeout);
  383. wait_until_user_started(Timeout) ->
  384. case whereis(user) of
  385. %% if user is not yet registered wait a tenth of a second and try again
  386. undefined -> timer:sleep(100), wait_until_user_started(Timeout - 100);
  387. _ -> ok
  388. end.
  389. add_test_paths(State) ->
  390. _ = [begin
  391. AppDir = rebar_app_info:out_dir(App),
  392. %% ignore errors resulting from non-existent directories
  393. _ = code:add_path(filename:join([AppDir, "test"]))
  394. end || App <- rebar_state:project_apps(State)],
  395. _ = code:add_path(filename:join([rebar_dir:base_dir(State), "test"])),
  396. ok.
  397. % First try the --config flag, then try the relx sys_config
  398. -spec find_config(rebar_state:t()) -> [[tuple()]] | no_config.
  399. find_config(State) ->
  400. case first_value([fun find_config_option/1,
  401. fun find_config_rebar/1,
  402. fun find_config_relx/1], State) of
  403. no_value ->
  404. no_config;
  405. Filename when is_list(Filename) ->
  406. rebar_file_utils:consult_config(State, Filename)
  407. end.
  408. -spec first_value([Fun], State) -> no_value | Value when
  409. Value :: any(),
  410. State :: rebar_state:t(),
  411. Fun :: fun ((State) -> no_value | Value).
  412. first_value([], _) -> no_value;
  413. first_value([Fun | Rest], State) ->
  414. case Fun(State) of
  415. no_value ->
  416. first_value(Rest, State);
  417. Value ->
  418. Value
  419. end.
  420. debug_get_value(Key, List, Default, Description) ->
  421. case proplists:get_value(Key, List, Default) of
  422. Default -> Default;
  423. Value ->
  424. ?DEBUG(Description, []),
  425. Value
  426. end.
  427. -spec find_config_option(rebar_state:t()) -> Filename::list() | no_value.
  428. find_config_option(State) ->
  429. {Opts, _} = rebar_state:command_parsed_args(State),
  430. debug_get_value(config, Opts, no_value,
  431. "Found config from command line option.").
  432. -spec find_config_rebar(rebar_state:t()) -> [tuple()] | no_value.
  433. find_config_rebar(State) ->
  434. debug_get_value(config, rebar_state:get(State, shell, []), no_value,
  435. "Found config from rebar config file.").
  436. -spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value.
  437. find_config_relx(State) ->
  438. debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value,
  439. "Found config from relx.").