Skip to content

Commit 9651afa

Browse files
authored
Add support for use_stdio option to System.shell and Mix.Shell.cmd (#13580)
1 parent 18db522 commit 9651afa

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

lib/elixir/lib/system.ex

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,11 @@ defmodule System do
10531053
10541054
* `:arg0` - sets the command arg0
10551055
1056-
* `:stderr_to_stdout` - redirects stderr to stdout when `true`
1056+
* `:stderr_to_stdout` - redirects stderr to stdout when `true`, no effect
1057+
if `use_stdio` is `false``.
1058+
1059+
* `:use_stdio` - `true` by default, setting it to false allows direct
1060+
interaction with the terminal from the callee
10571061
10581062
* `:parallelism` - when `true`, the VM will schedule port tasks to improve
10591063
parallelism in the system. If set to `false`, the VM will try to perform
@@ -1179,6 +1183,13 @@ defmodule System do
11791183
defp cmd_opts([{:stderr_to_stdout, false} | t], opts, into, line),
11801184
do: cmd_opts(t, opts, into, line)
11811185

1186+
defp cmd_opts([{:use_stdio, false} | t], opts, into, line),
1187+
do: cmd_opts(t, [:nouse_stdio | List.delete(opts, :use_stdio)], into, line)
1188+
1189+
# use_stdio is true by default, do nothing but match it
1190+
defp cmd_opts([{:use_stdio, true} | t], opts, into, line),
1191+
do: cmd_opts(t, opts, into, line)
1192+
11821193
defp cmd_opts([{:parallelism, bool} | t], opts, into, line) when is_boolean(bool),
11831194
do: cmd_opts(t, [{:parallelism, bool} | opts], into, line)
11841195

@@ -1192,8 +1203,12 @@ defmodule System do
11921203
defp cmd_opts([{key, val} | _], _opts, _into, _line),
11931204
do: raise(ArgumentError, "invalid option #{inspect(key)} with value #{inspect(val)}")
11941205

1195-
defp cmd_opts([], opts, into, line),
1196-
do: {into, line, opts}
1206+
defp cmd_opts([], opts, into, line) do
1207+
if :stderr_to_stdout in opts and :nouse_stdio in opts,
1208+
do: raise(ArgumentError, "cannot use `stderr_to_stdout: true` and `use_stdio: false`")
1209+
1210+
{into, line, opts}
1211+
end
11971212

11981213
defp validate_env(enum) do
11991214
Enum.map(enum, fn

lib/elixir/test/elixir/system_test.exs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ defmodule SystemTest do
110110
env: %{"foo" => "bar", "baz" => nil},
111111
arg0: "echo",
112112
stderr_to_stdout: true,
113-
parallelism: true
113+
parallelism: true,
114+
use_stdio: true
114115
]
115116

116117
assert {["hello\r\n"], 0} = System.cmd("cmd", ~w[/c echo hello], opts)
@@ -146,7 +147,8 @@ defmodule SystemTest do
146147
cd: File.cwd!(),
147148
env: %{"foo" => "bar", "baz" => nil},
148149
stderr_to_stdout: true,
149-
parallelism: true
150+
parallelism: true,
151+
use_stdio: true
150152
]
151153

152154
assert {["bar\r\n"], 0} = System.shell("echo %foo%", opts)
@@ -167,12 +169,39 @@ defmodule SystemTest do
167169
env: %{"foo" => "bar", "baz" => nil},
168170
arg0: "echo",
169171
stderr_to_stdout: true,
170-
parallelism: true
172+
parallelism: true,
173+
use_stdio: true
171174
]
172175

173176
assert {["hello\n"], 0} = System.cmd("echo", ["hello"], opts)
174177
end
175178

179+
test "cmd/3 (can't use `use_stdio: false, stderr_to_stdout: true`)" do
180+
opts = [
181+
into: [],
182+
cd: File.cwd!(),
183+
env: %{"foo" => "bar", "baz" => nil},
184+
arg0: "echo",
185+
stderr_to_stdout: true,
186+
use_stdio: false
187+
]
188+
189+
message = ~r"cannot use `stderr_to_stdout: true` and `use_stdio: false`"
190+
191+
assert_raise ArgumentError, message, fn ->
192+
System.cmd("echo", ["hello"], opts)
193+
end
194+
end
195+
196+
test "cmd/3 (`use_stdio: false`)" do
197+
opts = [
198+
into: [],
199+
use_stdio: false
200+
]
201+
202+
assert {[], 0} = System.cmd("echo", ["hello"], opts)
203+
end
204+
176205
test "cmd/3 by line" do
177206
assert {["hello", "world"], 0} =
178207
System.cmd("echo", ["hello\nworld"], into: [], lines: 1024)
@@ -228,11 +257,16 @@ defmodule SystemTest do
228257
into: [],
229258
cd: File.cwd!(),
230259
env: %{"foo" => "bar", "baz" => nil},
231-
stderr_to_stdout: true
260+
stderr_to_stdout: true,
261+
use_stdio: true
232262
]
233263

234264
assert {["bar\n"], 0} = System.shell("echo $foo", opts)
235265
end
266+
267+
test "shell/2 (non-interactive)" do
268+
assert {[], 0} = System.shell("echo hello", into: [], use_stdio: false)
269+
end
236270
end
237271

238272
@tag :unix

lib/mix/lib/mix/shell.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ defmodule Mix.Shell do
103103
104104
* `:cd` *(since v1.11.0)* - the directory to run the command in
105105
106-
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
106+
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true, unless use_stdio is set to false
107+
108+
* `:use_stdio` - controls whether the command should use stdin / stdout / stdrr, defaults to true
107109
108110
* `:env` - a list of environment variables, defaults to `[]`
109111
@@ -119,11 +121,13 @@ defmodule Mix.Shell do
119121
callback
120122
end
121123

124+
use_stdio = Keyword.get(options, :use_stdio, true)
125+
122126
options =
123127
options
124-
|> Keyword.take([:cd, :stderr_to_stdout, :env])
128+
|> Keyword.take([:cd, :stderr_to_stdout, :env, :use_stdio])
125129
|> Keyword.put(:into, %Mix.Shell{callback: callback})
126-
|> Keyword.put_new(:stderr_to_stdout, true)
130+
|> Keyword.put_new(:stderr_to_stdout, use_stdio)
127131

128132
{_, status} = System.shell(command, options)
129133
status

0 commit comments

Comments
 (0)