Do not deprecate Mix.shell.cmd/3

Between the two evils of hardcoding a shell name on the
Git SCM and deprecating (or enlarging) the shell API, we
prefer the former.
This commit is contained in:
José Valim
2017-10-14 23:19:32 +02:00
parent 73ab6b8ead
commit b2e5b69ae8
11 changed files with 70 additions and 103 deletions
-4
View File
@@ -59,10 +59,6 @@
* [ExUnit.Formatter] `:case_started` and `:case_finished` events are deprecated in favor of `:module_started` and `:module_finished`
#### Mix
* [Mix.Shell] The `cmd/2` callback is deprecated in favor of `System.cmd/3` and `Mix.Shell.cmd/3`
### 4. Deprecations
#### Elixir
+10 -1
View File
@@ -107,6 +107,8 @@ defmodule Mix.SCM.Git do
end
defp checkout(_path, opts) do
Mix.shell.print_app()
# Set configuration
sparse_toggle(opts)
update_origin(opts[:git])
@@ -244,13 +246,20 @@ defmodule Mix.SCM.Git do
:ok
end
defp git!(args, into \\ %Mix.Shell{}) do
defp git!(args, into \\ default_into()) do
case System.cmd("git", args, into: into, stderr_to_stdout: true) do
{response, 0} -> response
{_, _} -> Mix.raise("Command \"git #{Enum.join(args, " ")}\" failed")
end
end
defp default_into() do
case Mix.shell do
Mix.Shell.IO -> IO.stream(:stdio, :line)
_ -> ""
end
end
defp assert_git! do
case Mix.State.fetch(:git_available) do
{:ok, true} ->
+38 -63
View File
@@ -14,9 +14,27 @@ defmodule Mix.Shell do
@callback error(message :: IO.ANSI.ansidata()) :: :ok
@doc """
Writes data directly into the shell.
Executes the given command and returns its exit status.
"""
@callback write(message :: binary) :: :ok
@callback cmd(command :: String.t) :: integer
@doc """
Executes the given command and returns its exit status.
## Options
* `:print_app` - when `false`, does not print the app name
when the command outputs something
* `:stderr_to_stdout` - when `false`, does not redirect
stderr to stdout
* `:quiet` - when `true`, do not print the command output
* `:env` - environment options to the executed command
"""
@callback cmd(command :: String.t, options :: keyword) :: integer
@doc """
Prompts the user for input.
@@ -34,34 +52,6 @@ defmodule Mix.Shell do
"""
@callback print_app() :: :ok
@doc """
A collectable shell struct.
"""
defstruct print_app?: true
defimpl Collectable do
def into(%Mix.Shell{print_app?: print_app?} = original) do
fun = fn
{:cont, shell}, {:cont, data} ->
shell.write(data)
{:cont, shell}
{:print, shell}, {:cont, data} ->
shell.print_app()
shell.write(data)
{:cont, shell}
_, _ ->
original
end
case print_app? do
true -> {{:print, Mix.shell()}, fun}
false -> {{:cont, Mix.shell()}, fun}
end
end
end
@doc """
Returns the printable app name.
@@ -77,8 +67,20 @@ defmodule Mix.Shell do
Mix.ProjectStack.printable_app_name()
end
@doc false
# TODO: Deprecate on Mix v1.8
@doc """
Executes the given `command` as a shell command and
invokes the `callback` for the streamed response.
This is most commonly used by shell implementations
but can also be invoked directly.
## Options
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
* `:env` - a list of environment variables, defaults to `[]`
* `:quiet` - overrides the callback to no-op
"""
def cmd(command, options, callback) when is_function(callback, 1) do
callback =
if Keyword.get(options, :quiet, false) do
@@ -87,33 +89,6 @@ defmodule Mix.Shell do
callback
end
fun = fn
_, {:cont, data} -> callback.(data)
_, _ -> :ok
end
cmd(command, options, :ok, fun)
end
def cmd(command, callback) when is_function(callback, 1) do
cmd(command, [], callback)
end
@doc """
Executes the given `string` as a shell command.
* `:into` - a collectable to print the result to, defaults to `""`
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
* `:env` - a list of environment variables, defaults to `[]`
"""
def cmd(command, options) when is_binary(command) and is_list(options) do
collectable = Keyword.get(options, :into, "")
{acc, callback} = Collectable.into(collectable)
cmd(command, options, acc, callback)
end
defp cmd(command, options, acc, callback) do
env = validate_env(Keyword.get(options, :env, []))
args =
@@ -125,16 +100,16 @@ defmodule Mix.Shell do
opts = [:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env} | args]
port = Port.open({:spawn, shell_command(command)}, opts)
port_read(port, acc, callback)
port_read(port, callback)
end
defp port_read(port, acc, callback) do
defp port_read(port, callback) do
receive do
{^port, {:data, data}} ->
port_read(port, callback.(acc, {:cont, data}), callback)
_ = callback.(data)
port_read(port, callback)
{^port, {:exit_status, status}} ->
callback.(acc, :done)
status
end
end
+4 -7
View File
@@ -19,11 +19,6 @@ defmodule Mix.Shell.IO do
:ok
end
@doc """
Writes data directly into the shell.
"""
defdelegate write(data), to: IO
@doc """
Prints the given ANSI message to the shell followed by a newline.
"""
@@ -66,8 +61,10 @@ defmodule Mix.Shell.IO do
[:red, :bright, message]
end
@doc false
# TODO: Deprecate on Elixir v1.8
@doc """
Executes the given command and prints its output
to stdout as it comes.
"""
def cmd(command, opts \\ []) do
print_app? = Keyword.get(opts, :print_app, true)
+4 -10
View File
@@ -65,14 +65,6 @@ defmodule Mix.Shell.Process do
end
end
@doc """
Forwards the message to the current process.
"""
def write(data) do
send(self(), {:mix_shell, :write, [data]})
:ok
end
@doc """
Forwards the message to the current process.
"""
@@ -154,8 +146,10 @@ defmodule Mix.Shell.Process do
end
end
@doc false
# TODO: Deprecate on Elixir v1.8
@doc """
Executes the given command and forwards its messages to
the current process.
"""
def cmd(command, opts \\ []) do
print_app? = Keyword.get(opts, :print_app, true)
+3 -7
View File
@@ -14,11 +14,6 @@ defmodule Mix.Shell.Quiet do
"""
defdelegate print_app, to: Mix.Shell.IO
@doc """
Writes nothing to the shell.
"""
def write(_data), do: :ok
@doc """
Prints nothing to the shell.
"""
@@ -44,8 +39,9 @@ defmodule Mix.Shell.Quiet do
"""
defdelegate yes?(message), to: Mix.Shell.IO
@doc false
# TODO: Deprecate on Elixir v1.8
@doc """
Executes the given command quietly without outputting anything.
"""
def cmd(command, opts \\ []) do
Mix.Shell.cmd(command, opts, fn data -> data end)
end
+1 -1
View File
@@ -24,7 +24,7 @@ defmodule Mix.Tasks.Cmd do
{args, apps} = parse_apps(args, [])
if apps == [] or Mix.Project.config()[:app] in apps do
case Mix.Shell.cmd(Enum.join(args, " "), into: %Mix.Shell{}) do
case Mix.shell.cmd(Enum.join(args, " ")) do
0 -> :ok
status -> exit(status)
end
+1 -1
View File
@@ -265,7 +265,7 @@ defmodule Mix.Tasks.Deps.Compile do
File.cd!(opts[:dest], fn ->
env = [{"ERL_LIBS", Path.join(config[:env_path], "lib")}] ++ env
if Mix.Shell.cmd(command, env: env, into: %Mix.Shell{print_app?: print_app?}) != 0 do
if Mix.shell.cmd(command, env: env, print_app: print_app?) != 0 do
Mix.raise(
"Could not compile dependency #{inspect(app)}, \"#{command}\" command failed. " <>
"You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
+4 -4
View File
@@ -202,8 +202,8 @@ defmodule Mix.RebarTest do
assert_received {:mix_shell, :info, ["* Getting git_rebar" <> _]}
Mix.Tasks.Deps.Compile.run([])
assert_received {:mix_shell, :write, ["===> Compiling git_rebar\n"]}
assert_received {:mix_shell, :write, ["===> Compiling rebar_dep\n"]}
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
assert :git_rebar.any_function() == :ok
assert :rebar_dep.any_function() == :ok
@@ -233,8 +233,8 @@ defmodule Mix.RebarTest do
assert_received {:mix_shell, :info, ["* Getting git_rebar " <> _]}
Mix.Tasks.Deps.Compile.run([])
assert_received {:mix_shell, :write, ["===> Compiling git_rebar\n"]}
assert_received {:mix_shell, :write, ["===> Compiling rebar_dep\n"]}
assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
assert :git_rebar.any_function() == :ok
assert :rebar_dep.any_function() == :ok
+1 -1
View File
@@ -11,7 +11,7 @@ defmodule Mix.ShellTest do
Mix.shell(Mix.Shell.IO)
assert capture_io(fn ->
assert Mix.Shell.cmd("echo first && echo second", into: %Mix.Shell{}) == 0
assert Mix.shell.cmd("echo first && echo second") == 0
end)
|> String.replace(" \n", "\n") == "first\nsecond\n"
after
+4 -4
View File
@@ -9,9 +9,9 @@ defmodule Mix.Tasks.CmdTest do
Mix.Task.run("cmd", ["echo", "hello"])
nl = os_newline()
assert_received {:mix_shell, :info, ["==> bar"]}
assert_received {:mix_shell, :write, ["hello" <> ^nl]}
assert_received {:mix_shell, :run, ["hello" <> ^nl]}
assert_received {:mix_shell, :info, ["==> foo"]}
assert_received {:mix_shell, :write, ["hello" <> ^nl]}
assert_received {:mix_shell, :run, ["hello" <> ^nl]}
end)
end
end
@@ -22,9 +22,9 @@ defmodule Mix.Tasks.CmdTest do
Mix.Task.run("cmd", ["--app", "bar", "echo", "hello"])
nl = os_newline()
assert_received {:mix_shell, :info, ["==> bar"]}
assert_received {:mix_shell, :write, ["hello" <> ^nl]}
assert_received {:mix_shell, :run, ["hello" <> ^nl]}
refute_received {:mix_shell, :info, ["==> foo"]}
refute_received {:mix_shell, :write, ["hello" <> ^nl]}
refute_received {:mix_shell, :run, ["hello" <> ^nl]}
end)
end
end