Skip to content

Commit b3710de

Browse files
committed
Always start IEx shell via -user
There is some uncertainty as to wether it will be possible to start an IEx shell dynamically from a -noshell environment. Therefore, this PR simplifies the shell booting to still rely on -user, as it did earlier than Erlang/OTP 26.
1 parent fc0c487 commit b3710de

File tree

12 files changed

+108
-214
lines changed

12 files changed

+108
-214
lines changed

Diff for: bin/elixir

+2-1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ SELF=$(readlink_f "$0")
218218
SCRIPT_PATH=$(dirname "$SELF")
219219

220220
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
221+
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
221222

222223
if [ "$OS" != "Windows_NT" ] && [ -z "$NO_COLOR" ]; then
223224
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
@@ -228,7 +229,7 @@ fi
228229
ERTS_BIN=
229230
ERTS_BIN="$ERTS_BIN"
230231

231-
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_$MODE $ERL "$@"
232+
set -- "$ERTS_BIN$ERL_EXEC" -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS $ERL "$@"
232233

233234
if [ -n "$RUN_ERL_PIPE" ]; then
234235
ESCAPED=""

Diff for: bin/elixir.bat

+3-5
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,11 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
161161
if %errorlevel% == 0 (
162162
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
163163
)
164-
if defined useIEx (
165-
set beforeExtra=-s elixir start_iex !beforeExtra!
166-
) else (
167-
set beforeExtra=-s elixir start_cli !beforeExtra!
164+
if not defined useIEx (
165+
set beforeExtra=-noshell -s elixir start_cli !beforeExtra!
168166
)
169167

170-
set beforeExtra=-noshell -elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!
168+
set beforeExtra=-elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!
171169

172170
if defined ELIXIR_CLI_DRY_RUN (
173171
if defined useWerl (

Diff for: lib/elixir/lib/system.ex

-16
Original file line numberDiff line numberDiff line change
@@ -305,22 +305,6 @@ defmodule System do
305305
:elixir_config.get(:no_halt)
306306
end
307307

308-
@doc """
309-
Waits until the system boots.
310-
311-
Calling this function blocks until all of ARGV is processed.
312-
Inside a release, this means the boot script and then ARGV
313-
have been processed. This is only useful for those implementing
314-
custom shells/consoles on top of Elixir.
315-
316-
However, be careful to not invoke this command from within
317-
the process that is processing the command line arguments,
318-
as doing so would lead to a deadlock.
319-
"""
320-
@doc since: "1.15.0"
321-
@spec wait_until_booted() :: :ok
322-
defdelegate wait_until_booted(), to: :elixir_config
323-
324308
@doc """
325309
Current working directory.
326310

Diff for: lib/elixir/src/elixir.erl

+4-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
%% private to the Elixir compiler and reserved to be used by Elixir only.
33
-module(elixir).
44
-behaviour(application).
5-
-export([start_cli/0, start/0, start_iex/0]).
5+
-export([start_cli/0, start/0]).
66
-export([start/2, stop/1, config_change/3]).
77
-export([
88
string_to_tokens/5, tokens_to_quoted/3, 'string_to_quoted!'/5,
@@ -176,16 +176,12 @@ check_file_encoding(Encoding) ->
176176
end.
177177

178178
%% Boot and process given options. Invoked by Elixir's script.
179-
180-
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26.
181-
%% TODO: Remove IEx.CLI module
182-
%% TODO: Replace "-user elixir" and "-s elixir start_$MODE"
183-
%% by calls to "-s elixir cli" and "-e iex:cli()"
179+
%% TODO: Delete prim_tty branches on Erlang/OTP 26.
184180

185181
start() ->
186182
case code:ensure_loaded(prim_tty) of
187183
{module, _} ->
188-
user_drv:start(#{initial_shell => noshell});
184+
user_drv:start(#{initial_shell => iex:shell()});
189185
{error, _} ->
190186
case init:get_argument(elixir_root) of
191187
{ok, [[Root]]} -> code:add_patha(Root ++ "/iex/ebin");
@@ -207,18 +203,7 @@ start_cli() ->
207203
{error, _} -> ok
208204
end,
209205

210-
'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
211-
elixir_config:booted().
212-
213-
start_iex() ->
214-
case code:ensure_loaded(prim_tty) of
215-
{module, _} ->
216-
start_cli(),
217-
iex:cli();
218-
219-
{error, _} ->
220-
ok
221-
end.
206+
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
222207

223208
%% EVAL HOOKS
224209

Diff for: lib/elixir/src/elixir_config.erl

+9-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-module(elixir_config).
22
-compile({no_auto_import, [get/1]}).
3-
-export([new/1, warn/2, serial/1, booted/0, wait_until_booted/0]).
3+
-export([new/1, warn/2, serial/1]).
44
-export([static/1, is_bootstrap/0, identifier_tokenizer/0]).
55
-export([delete/1, put/2, get/1, get/2, update/2, get_and_put/2]).
66
-export([start_link/0, init/1, handle_call/3, handle_cast/2]).
@@ -48,11 +48,6 @@ delete(?MODULE) ->
4848

4949
%% MISC
5050

51-
booted() ->
52-
gen_server:call(?MODULE, booted, infinity).
53-
wait_until_booted() ->
54-
gen_server:call(?MODULE, wait_until_booted, infinity).
55-
5651
serial(Fun) ->
5752
gen_server:call(?MODULE, {serial, Fun}, infinity).
5853

@@ -75,27 +70,19 @@ start_link() ->
7570
init(?MODULE) ->
7671
{ok, []}.
7772

78-
handle_call(booted, _From, Booted) when is_list(Booted) ->
79-
[gen_server:reply(Caller, ok) || Caller <- Booted],
80-
{reply, ok, done};
81-
handle_call(wait_until_booted, From, Booted) ->
82-
if
83-
is_list(Booted) -> {noreply, [From | Booted]};
84-
Booted =:= done -> {reply, ok, Booted}
85-
end;
86-
handle_call({serial, Fun}, _From, Booted) ->
87-
{reply, Fun(), Booted};
88-
handle_call({put, Key, Value}, _From, Booted) ->
73+
handle_call({serial, Fun}, _From, State) ->
74+
{reply, Fun(), State};
75+
handle_call({put, Key, Value}, _From, State) ->
8976
ets:insert(?MODULE, {Key, Value}),
90-
{reply, ok, Booted};
91-
handle_call({update, Key, Fun}, _From, Booted) ->
77+
{reply, ok, State};
78+
handle_call({update, Key, Fun}, _From, State) ->
9279
Value = Fun(get(Key)),
9380
ets:insert(?MODULE, {Key, Value}),
94-
{reply, Value, Booted};
95-
handle_call({get_and_put, Key, Value}, _From, Booted) ->
81+
{reply, Value, State};
82+
handle_call({get_and_put, Key, Value}, _From, State) ->
9683
OldValue = get(Key),
9784
ets:insert(?MODULE, {Key, Value}),
98-
{reply, OldValue, Booted}.
85+
{reply, OldValue, State}.
9986

10087
handle_cast(Cast, Tab) ->
10188
{stop, {bad_cast, Cast}, Tab}.

Diff for: lib/elixir/src/elixir_errors.erl

+3-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,9 @@ print_error(Meta, Env, Module, Desc) ->
299299
%% Compilation error.
300300

301301
-spec compile_error(#{file := binary(), _ => _}) -> no_return().
302-
compile_error(#{module := Module, file := File}) when Module /= nil ->
302+
%% We check for the lexical tracker because pry() inside a module
303+
%% will have the environment but not a tracker.
304+
compile_error(#{module := Module, file := File, lexical_tracker := LT}) when Module /= nil, LT /= nil ->
303305
Inspected = elixir_aliases:inspect(Module),
304306
Message = io_lib:format("cannot compile module ~ts (errors have been logged)", [Inspected]),
305307
compile_error([], File, Message);

Diff for: lib/elixir/src/iex.erl

+42-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-module(iex).
2-
-export([cli/0]).
2+
-export([start/0, start/2, shell/0, sync_remote/2]).
33

44
%% Manual tests for changing the CLI boot.
55
%%
@@ -24,28 +24,57 @@
2424
%% 4. Finally, in some other circumstances, printing messages may become
2525
%% borked. This can be verified with:
2626
%%
27-
%% $ iex -e ":logger.info('foo~nbar', [])"
27+
%% $ iex -e ":logger.info(~c'foo~nbar', [])"
2828
%%
2929
%% By the time those instructions have been written, all tests above pass.
30-
cli() ->
30+
31+
start() ->
32+
start([], {elixir_utils, noop, []}).
33+
34+
start(Opts, MFA) ->
35+
{ok, _} = application:ensure_all_started(elixir),
36+
{ok, _} = application:ensure_all_started(iex),
37+
3138
spawn(fun() ->
32-
elixir_config:wait_until_booted(),
33-
(shell:whereis() =:= undefined) andalso start_shell()
39+
case init:notify_when_started(self()) of
40+
started -> ok;
41+
_ -> init:wait_until_started()
42+
end,
43+
44+
ok = io:setopts([{binary, true}, {encoding, unicode}]),
45+
'Elixir.IEx.Server':run_from_shell(Opts, MFA)
3446
end).
3547

36-
start_shell() ->
48+
shell() ->
3749
Args = init:get_plain_arguments(),
38-
Opts = [{remote, get_remsh(Args)}, {dot_iex_path, get_dot_iex(Args)}, {on_eof, halt}],
3950

40-
case 'Elixir.IEx':shell(Opts) of
41-
{ok, _Shell} ->
42-
ok;
51+
case get_remsh(Args) of
52+
nil ->
53+
start_mfa(Args, {elixir, start_cli, []});
4354

44-
{error, Reason} ->
45-
io:format(standard_error, "Could not start IEx CLI due to reason: ~tp", [Reason]),
46-
erlang:halt(1)
55+
Remote ->
56+
Ref = make_ref(),
57+
58+
Parent =
59+
spawn_link(fun() ->
60+
receive
61+
{'begin', Ref, Other} ->
62+
elixir:start_cli(),
63+
Other ! {done, Ref}
64+
end
65+
end),
66+
67+
{remote, Remote, start_mfa(Args, {?MODULE, sync_remote, [Parent, Ref]})}
4768
end.
4869

70+
sync_remote(Parent, Ref) ->
71+
Parent ! {'begin', Ref, self()},
72+
receive {done, Ref} -> ok end.
73+
74+
start_mfa(Args, MFA) ->
75+
Opts = [{dot_iex_path, get_dot_iex(Args)}, {on_eof, halt}],
76+
{?MODULE, start, [Opts, MFA]}.
77+
4978
get_dot_iex(["--dot-iex", H | _]) -> elixir_utils:characters_to_binary(H);
5079
get_dot_iex([_ | T]) -> get_dot_iex(T);
5180
get_dot_iex([]) -> nil.

Diff for: lib/iex/lib/iex.ex

+5-66
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ defmodule IEx do
226226
alternate between them. Let's give it a try:
227227
228228
User switch command
229-
--> s 'Elixir.IEx'
229+
--> s iex
230230
--> c
231231
232232
The command above will start a new shell and connect to it.
@@ -858,71 +858,10 @@ defmodule IEx do
858858

859859
## CLI
860860

861-
# This is a callback invoked by Erlang shell utilities
862-
# when someone presses Ctrl+G and adds `s 'Elixir.IEx'`.
861+
# TODO: Remove me on Elixir v1.20+
863862
@doc false
864-
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
865-
# TODO: Expose this as iex:start() instead on Erlang/OTP 26+
866-
# TODO: Keep only this branch, delete optional args and mfa,
867-
# and delete IEx.Server.run_from_shell/2 on Erlang/OTP 26+
868-
if Code.ensure_loaded?(:prim_tty) do
869-
spawn(fn ->
870-
{:ok, _} = Application.ensure_all_started(:iex)
871-
:ok = :io.setopts(binary: true, encoding: :unicode)
872-
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
873-
IEx.Server.run([register: false] ++ opts)
874-
end)
875-
else
876-
spawn(fn ->
877-
case :init.notify_when_started(self()) do
878-
:started -> :ok
879-
_ -> :init.wait_until_started()
880-
end
881-
882-
{:ok, _} = Application.ensure_all_started(:iex)
883-
:ok = :io.setopts(binary: true, encoding: :unicode)
884-
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
885-
IEx.Server.run_from_shell(opts, mfa)
886-
end)
887-
end
888-
end
889-
890-
# TODO: The idea is to expose this as a public API once we require
891-
# Erlang/OTP 26 but it may not be possible. In such cases, we may
892-
# want to move this to another module.
893-
# See https://github.com/erlang/otp/issues/8113#issuecomment-1941613281
894-
@compile {:no_warn_undefined, {:shell, :start_interactive, 1}}
895-
896-
@doc false
897-
def shell(opts) do
898-
{remote, opts} = Keyword.pop(opts, :remote)
899-
ref = make_ref()
900-
mfa = {__MODULE__, :__shell__, [self(), ref, opts]}
901-
902-
shell =
903-
if remote do
904-
{:remote, to_charlist(remote), mfa}
905-
else
906-
mfa
907-
end
908-
909-
case :shell.start_interactive(shell) do
910-
:ok ->
911-
receive do
912-
{^ref, shell} -> {:ok, shell}
913-
after
914-
15_000 -> {:error, :timeout}
915-
end
916-
917-
{:error, reason} ->
918-
{:error, reason}
919-
end
920-
end
921-
922-
@doc false
923-
def __shell__(parent, ref, opts) do
924-
pid = start(opts)
925-
send(parent, {ref, pid})
926-
pid
863+
def start do
864+
IO.warn("Use \"s iex\" instead")
865+
:iex.start()
927866
end
928867
end

Diff for: lib/iex/lib/iex/broker.ex

-41
Original file line numberDiff line numberDiff line change
@@ -75,54 +75,13 @@ defmodule IEx.Broker do
7575
GenServer.call(broker_pid, {:refuse, take_ref})
7676
end
7777

78-
@doc """
79-
Asks to IO if we want to take over.
80-
"""
81-
def take_over?(location, whereami, opts) do
82-
evaluator = opts[:evaluator] || self()
83-
message = "Request to pry #{inspect(evaluator)} at #{location}#{whereami}"
84-
interrupt = IEx.color(:eval_interrupt, "#{message}\nAllow? [Yn] ")
85-
yes?(IO.gets(:stdio, interrupt))
86-
end
87-
88-
defp yes?(string) when is_binary(string),
89-
do: String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
90-
91-
defp yes?(charlist) when is_list(charlist),
92-
do: yes?(List.to_string(charlist))
93-
94-
defp yes?(_), do: false
95-
9678
@doc """
9779
Client requests a takeover.
9880
"""
9981
@spec take_over(binary, iodata, keyword) ::
10082
{:ok, server :: pid, group_leader :: pid, counter :: integer}
10183
| {:error, :no_iex | :refused | atom()}
10284
def take_over(location, whereami, opts) do
103-
case take_over_existing(location, whereami, opts) do
104-
{:error, :no_iex} ->
105-
cond do
106-
# TODO: Remove this check on Erlang/OTP 26+ and {:error, :no_iex} return
107-
not Code.ensure_loaded?(:prim_tty) ->
108-
{:error, :no_iex}
109-
110-
take_over?(location, whereami, opts) ->
111-
case IEx.shell(opts) do
112-
{:ok, shell} -> {:ok, shell, Process.group_leader(), 1}
113-
{:error, reason} -> {:error, reason}
114-
end
115-
116-
true ->
117-
{:error, :refused}
118-
end
119-
120-
other ->
121-
other
122-
end
123-
end
124-
125-
defp take_over_existing(location, whereami, opts) do
12685
case GenServer.whereis(@name) do
12786
nil -> {:error, :no_iex}
12887
_pid -> GenServer.call(@name, {:take_over, location, whereami, opts}, :infinity)

0 commit comments

Comments
 (0)