No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

448 líneas
17 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.
hace 9 años
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.
hace 9 años
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.
hace 9 años
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.
hace 9 años
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.
hace 9 años
hace 10 años
  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. {script_file, undefined, "script", string,
  61. "Path to an escript file to run before "
  62. "starting the project apps. Defaults to "
  63. "rebar.config {shell, [{script_file, File}]} "
  64. "if not specified."},
  65. {apps, undefined, "apps", string,
  66. "A list of apps to boot before starting the "
  67. "shell. (E.g. --apps app1,app2,app3) Defaults "
  68. "to rebar.config {shell, [{apps, Apps}]} or "
  69. "relx apps if not specified."}]}
  70. ])
  71. ),
  72. {ok, State1}.
  73. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  74. do(Config) ->
  75. shell(Config),
  76. {ok, Config}.
  77. -spec format_error(any()) -> iolist().
  78. format_error(Reason) ->
  79. io_lib:format("~p", [Reason]).
  80. %% NOTE:
  81. %% this is an attempt to replicate `erl -pa ./ebin -pa deps/*/ebin`. it is
  82. %% mostly successful but does stop and then restart the user io system to get
  83. %% around issues with rebar being an escript and starting in `noshell` mode.
  84. %% it also lacks the ctrl-c interrupt handler that `erl` features. ctrl-c will
  85. %% immediately kill the script. ctrl-g, however, works fine
  86. shell(State) ->
  87. setup_name(State),
  88. setup_paths(State),
  89. setup_shell(),
  90. maybe_run_script(State),
  91. %% apps must be started after the change in shell because otherwise
  92. %% their application masters never gets the new group leader (held in
  93. %% their internal state)
  94. maybe_boot_apps(State),
  95. simulate_proc_lib(),
  96. true = register(rebar_agent, self()),
  97. {ok, GenState} = rebar_agent:init(State),
  98. gen_server:enter_loop(rebar_agent, [], GenState, {local, rebar_agent}, hibernate).
  99. info() ->
  100. "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n".
  101. setup_shell() ->
  102. OldUser = kill_old_user(),
  103. %% Test for support here
  104. NewUser = try erlang:open_port({spawn,'tty_sl -c -e'}, []) of
  105. Port when is_port(Port) ->
  106. true = port_close(Port),
  107. setup_new_shell()
  108. catch
  109. error:_ ->
  110. setup_old_shell()
  111. end,
  112. rewrite_leaders(OldUser, NewUser).
  113. kill_old_user() ->
  114. OldUser = whereis(user),
  115. %% terminate the current user's port, in a way that makes it shut down,
  116. %% but without taking down the supervision tree so that the escript doesn't
  117. %% fully die
  118. [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],
  119. user ! {'EXIT', P, normal}, % pretend the port died, then the port can die!
  120. OldUser.
  121. setup_new_shell() ->
  122. %% terminate the current user supervision structure
  123. ok = supervisor:terminate_child(kernel_sup, user),
  124. %% start a new shell (this also starts a new user under the correct group)
  125. _ = user_drv:start(),
  126. %% wait until user_drv and user have been registered (max 3 seconds)
  127. ok = wait_until_user_started(3000),
  128. whereis(user).
  129. setup_old_shell() ->
  130. %% scan all processes for any with references to the old user and save them to
  131. %% update later
  132. NewUser = rebar_user:start(), % hikack IO stuff with fake user
  133. NewUser = whereis(user),
  134. NewUser.
  135. rewrite_leaders(OldUser, NewUser) ->
  136. %% set any process that had a reference to the old user's group leader to the
  137. %% new user process. Catch the race condition when the Pid exited after the
  138. %% liveness check.
  139. _ = [catch erlang:group_leader(NewUser, Pid)
  140. || Pid <- erlang:processes(),
  141. proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
  142. is_process_alive(Pid)],
  143. %% Application masters have the same problem, but they hold the old group
  144. %% leader in their state and hold on to it. Re-point the processes whose
  145. %% leaders are application masters. This can mess up a few things around
  146. %% shutdown time, but is nicer than the current lock-up.
  147. OldMasters = [Pid
  148. || Pid <- erlang:processes(),
  149. Pid < NewUser, % only change old masters
  150. {_,Dict} <- [erlang:process_info(Pid, dictionary)],
  151. {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
  152. _ = [catch erlang:group_leader(NewUser, Pid)
  153. || Pid <- erlang:processes(),
  154. lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
  155. OldMasters)],
  156. try
  157. %% enable error_logger's tty output
  158. error_logger:swap_handler(tty),
  159. %% disable the simple error_logger (which may have been added multiple
  160. %% times). removes at most the error_logger added by init and the
  161. %% error_logger added by the tty handler
  162. remove_error_handler(3)
  163. catch
  164. E:R -> % may fail with custom loggers
  165. ?DEBUG("Logger changes failed for ~p:~p (~p)", [E,R,erlang:get_stacktrace()]),
  166. hope_for_best
  167. end.
  168. setup_paths(State) ->
  169. %% Add deps to path
  170. code:add_pathsa(rebar_state:code_paths(State, all_deps)),
  171. %% add project app test paths
  172. ok = add_test_paths(State).
  173. maybe_run_script(State) ->
  174. case first_value([fun find_script_option/1,
  175. fun find_script_rebar/1], State) of
  176. no_value ->
  177. ?DEBUG("No script_file specified.", []),
  178. ok;
  179. "none" ->
  180. ?DEBUG("Shell script execution skipped (--script none).", []),
  181. ok;
  182. RelFile ->
  183. File = filename:absname(RelFile),
  184. try run_script_file(File)
  185. catch
  186. C:E ->
  187. ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p",
  188. [File, C, E, erlang:get_stacktrace()])
  189. end
  190. end.
  191. -spec find_script_option(rebar_state:t()) -> no_value | list().
  192. find_script_option(State) ->
  193. {Opts, _} = rebar_state:command_parsed_args(State),
  194. debug_get_value(script_file, Opts, no_value,
  195. "Found script file from command line option.").
  196. -spec find_script_rebar(rebar_state:t()) -> no_value | list().
  197. find_script_rebar(State) ->
  198. Config = rebar_state:get(State, shell, []),
  199. %% Either a string, or undefined
  200. debug_get_value(script_file, Config, no_value,
  201. "Found script file from rebar config file.").
  202. run_script_file(File) ->
  203. ?DEBUG("Extracting escript from ~p", [File]),
  204. {ok, Script} = escript:extract(File, [compile_source]),
  205. Beam = proplists:get_value(source, Script),
  206. Mod = proplists:get_value(module, beam_lib:info(Beam)),
  207. ?DEBUG("Compiled escript as ~p", [Mod]),
  208. FakeFile = "/fake_path/" ++ atom_to_list(Mod),
  209. {module, Mod} = code:load_binary(Mod, FakeFile, Beam),
  210. ?DEBUG("Evaling ~p:main([]).", [Mod]),
  211. Result = Mod:main([]),
  212. ?DEBUG("Result: ~p", [Result]),
  213. Result.
  214. maybe_boot_apps(State) ->
  215. case find_apps_to_boot(State) of
  216. undefined ->
  217. %% try to read in sys.config file
  218. ok = reread_config(State);
  219. Apps ->
  220. %% load apps, then check config, then boot them.
  221. load_apps(Apps),
  222. ok = reread_config(State),
  223. boot_apps(Apps)
  224. end.
  225. simulate_proc_lib() ->
  226. FakeParent = spawn_link(fun() -> timer:sleep(infinity) end),
  227. put('$ancestors', [FakeParent]),
  228. put('$initial_call', {rebar_agent, init, 1}).
  229. setup_name(State) ->
  230. {Opts, _} = rebar_state:command_parsed_args(State),
  231. case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of
  232. {undefined, undefined} ->
  233. ok;
  234. {Name, undefined} ->
  235. check_epmd(net_kernel:start([Name, longnames]));
  236. {undefined, SName} ->
  237. check_epmd(net_kernel:start([SName, shortnames]));
  238. {_, _} ->
  239. ?ABORT("Cannot have both short and long node names defined", [])
  240. end.
  241. check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) ->
  242. ?ERROR("Erlang Distribution failed, falling back to nonode@nohost. "
  243. "Verify that epmd is running and try again.",[]);
  244. check_epmd(_) ->
  245. ok.
  246. find_apps_to_boot(State) ->
  247. %% Try the shell_apps option
  248. case first_value([fun find_apps_option/1,
  249. fun find_apps_rebar/1,
  250. fun find_apps_relx/1], State) of
  251. no_value ->
  252. undefined;
  253. Apps ->
  254. Apps
  255. end.
  256. -spec find_apps_option(rebar_state:t()) -> no_value | [atom()].
  257. find_apps_option(State) ->
  258. {Opts, _} = rebar_state:command_parsed_args(State),
  259. case debug_get_value(apps, Opts, no_value,
  260. "Found shell apps from command line option.") of
  261. no_value -> no_value;
  262. AppsStr ->
  263. [ list_to_atom(AppStr)
  264. || AppStr <- string:tokens(AppsStr, " ,:") ]
  265. end.
  266. -spec find_apps_rebar(rebar_state:t()) -> no_value | list().
  267. find_apps_rebar(State) ->
  268. ShellOpts = rebar_state:get(State, shell, []),
  269. debug_get_value(apps, ShellOpts, no_value,
  270. "Found shell opts from command line option.").
  271. -spec find_apps_relx(rebar_state:t()) -> no_value | list().
  272. find_apps_relx(State) ->
  273. case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
  274. {_, _, Apps} ->
  275. ?DEBUG("Found shell apps from relx.", []),
  276. Apps;
  277. false ->
  278. no_value
  279. end.
  280. load_apps(Apps) ->
  281. [case application:load(App) of
  282. ok ->
  283. {ok, Ks} = application:get_all_key(App),
  284. load_apps(proplists:get_value(applications, Ks));
  285. _ ->
  286. error % will be caught when starting the app
  287. end || App <- normalize_load_apps(Apps),
  288. not lists:keymember(App, 1, application:loaded_applications())],
  289. ok.
  290. reread_config(State) ->
  291. case find_config(State) of
  292. no_config ->
  293. ok;
  294. ConfigList ->
  295. try
  296. [application:set_env(Application, Key, Val)
  297. || {Application, Items} <- ConfigList,
  298. {Key, Val} <- Items]
  299. catch _:_ ->
  300. ?ERROR("The configuration file submitted could not be read "
  301. "and will be ignored.", [])
  302. end,
  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. 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.").
  394. -spec consult_config(rebar_state:t(), string()) -> [tuple()].
  395. consult_config(State, Filename) ->
  396. Fullpath = filename:join(rebar_dir:root_dir(State), Filename),
  397. ?DEBUG("Loading configuration from ~p", [Fullpath]),
  398. case rebar_file_utils:try_consult(Fullpath) of
  399. [T] -> T;
  400. [] -> []
  401. end.