Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

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