diff --git a/CHANGELOG.md b/CHANGELOG.md index 49afb30a53..4240a6bbce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex index ecf05df460..6603fb8248 100644 --- a/lib/mix/lib/mix/scm/git.ex +++ b/lib/mix/lib/mix/scm/git.ex @@ -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} -> diff --git a/lib/mix/lib/mix/shell.ex b/lib/mix/lib/mix/shell.ex index 18406cf1e3..e3812b7d08 100644 --- a/lib/mix/lib/mix/shell.ex +++ b/lib/mix/lib/mix/shell.ex @@ -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 diff --git a/lib/mix/lib/mix/shell/io.ex b/lib/mix/lib/mix/shell/io.ex index d4b1072b20..252148db35 100644 --- a/lib/mix/lib/mix/shell/io.ex +++ b/lib/mix/lib/mix/shell/io.ex @@ -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) diff --git a/lib/mix/lib/mix/shell/process.ex b/lib/mix/lib/mix/shell/process.ex index c1dd448b19..a32dacd753 100644 --- a/lib/mix/lib/mix/shell/process.ex +++ b/lib/mix/lib/mix/shell/process.ex @@ -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) diff --git a/lib/mix/lib/mix/shell/quiet.ex b/lib/mix/lib/mix/shell/quiet.ex index ac37780860..e4634e97a5 100644 --- a/lib/mix/lib/mix/shell/quiet.ex +++ b/lib/mix/lib/mix/shell/quiet.ex @@ -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 diff --git a/lib/mix/lib/mix/tasks/cmd.ex b/lib/mix/lib/mix/tasks/cmd.ex index 0b7c998ff1..a93fe2b1a0 100644 --- a/lib/mix/lib/mix/tasks/cmd.ex +++ b/lib/mix/lib/mix/tasks/cmd.ex @@ -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 diff --git a/lib/mix/lib/mix/tasks/deps.compile.ex b/lib/mix/lib/mix/tasks/deps.compile.ex index 93d99373f2..1894532b37 100644 --- a/lib/mix/lib/mix/tasks/deps.compile.ex +++ b/lib/mix/lib/mix/tasks/deps.compile.ex @@ -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 " <> diff --git a/lib/mix/test/mix/rebar_test.exs b/lib/mix/test/mix/rebar_test.exs index 25f73a27cf..6bec356162 100644 --- a/lib/mix/test/mix/rebar_test.exs +++ b/lib/mix/test/mix/rebar_test.exs @@ -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 diff --git a/lib/mix/test/mix/shell_test.exs b/lib/mix/test/mix/shell_test.exs index 32ebf39c86..edb735b6b2 100644 --- a/lib/mix/test/mix/shell_test.exs +++ b/lib/mix/test/mix/shell_test.exs @@ -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 diff --git a/lib/mix/test/mix/tasks/cmd_test.exs b/lib/mix/test/mix/tasks/cmd_test.exs index 2b427300ad..fd1039746a 100644 --- a/lib/mix/test/mix/tasks/cmd_test.exs +++ b/lib/mix/test/mix/tasks/cmd_test.exs @@ -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