Improve shelling out on mix, closes #492

This commit is contained in:
José Valim
2012-11-30 11:22:48 +01:00
parent b04351f625
commit 6a68bef35a
8 changed files with 86 additions and 35 deletions
+6 -8
View File
@@ -1,10 +1,6 @@
defmodule Mix.CLI do
@moduledoc false
defmacrop exceptions do
[Mix.Error, Mix.NoTaskError, Mix.InvalidTaskError, Mix.NoProjectError, Mix.OutOfDateDepsError]
end
@doc """
Runs Mix according to the command line arguments.
"""
@@ -47,11 +43,13 @@ defmodule Mix.CLI do
rescue
# We only rescue exceptions in the mix namespace, all
# others pass through and will explode on the users face
exception in exceptions ->
if msg = exception.message do
Mix.shell.error msg
exception ->
if function_exported?(exception, :mix_error, 0) do
if msg = exception.message, do: Mix.shell.error "** (Mix) #{msg}"
else
raise(exception)
end
exit(1)
end
end
end
end
+5 -6
View File
@@ -1,22 +1,21 @@
defexception Mix.NoTaskError, task: nil do
defexception Mix.NoTaskError, task: nil, mix_error: true do
def message(exception) do
"The task #{task(exception)} could not be found"
end
end
defexception Mix.InvalidTaskError, task: nil do
defexception Mix.InvalidTaskError, task: nil, mix_error: true do
def message(exception) do
"The task #{task(exception)} does not respond to run/1"
end
end
defexception Mix.NoProjectError,
defexception Mix.NoProjectError, mix_error: true,
message: "Could not find a Mix.Project"
defexception Mix.Error,
message: nil
defexception Mix.Error, mix_error: true, message: nil
defexception Mix.OutOfDateDepsError, env: nil do
defexception Mix.OutOfDateDepsError, mix_error: true, env: nil do
def message(exception) do
"Some dependencies are out of date, please run `MIX_ENV=#{exception.env} mix deps.get` to proceed"
end
+14 -10
View File
@@ -38,20 +38,20 @@ defmodule Mix.SCM.Git do
def checkout(opts) do
path = opts[:path]
location = opts[:git]
maybe_error System.cmd(%b[git clone --quiet --no-checkout "#{location}" "#{path}"])
command = %b[git clone --no-checkout "#{location}" "#{path}"]
if checked_out?(opts) do
File.cd! path, fn -> do_checkout(opts) end
end
run_cmd_or_raise(command)
File.cd! path, fn -> do_checkout(opts) end
end
def update(opts) do
File.cd! opts[:path], fn ->
command = "git fetch --force --quiet"
command = "git fetch --force"
if opts[:tag] do
command = command <> " --tags"
end
maybe_error System.cmd(command)
run_cmd_or_raise(command)
do_checkout(opts)
end
end
@@ -64,10 +64,10 @@ defmodule Mix.SCM.Git do
defp do_checkout(opts) do
ref = get_lock_rev(opts[:lock]) || get_opts_rev(opts)
maybe_error System.cmd("git checkout --quiet #{ref}")
run_cmd_or_raise "git checkout --quiet #{ref}"
if opts[:submodules] do
maybe_error System.cmd("git submodule update --init --recursive")
run_cmd_or_raise "git submodule update --init --recursive"
end
get_lock(opts, true)
@@ -120,6 +120,10 @@ defmodule Mix.SCM.Git do
nil
end
defp maybe_error(""), do: :ok
defp maybe_error(other), do: Mix.shell.error(other)
defp run_cmd_or_raise(command) do
if Mix.shell.cmd(command) != 0 do
raise Mix.Error, message: "command `#{command}` failed"
end
true
end
end
+26
View File
@@ -19,4 +19,30 @@ defmodule Mix.Shell do
Asks the user for confirmation.
"""
defcallback yes?(message :: binary) :: any
@doc """
Executes the given command and returns
its exit status.
"""
defcallback cmd(command :: binary) :: integer
@doc """
An implementation of the command callback that
is shared accross different shells.
"""
def cmd(command, callback) do
port = Port.open({ :spawn, to_char_list(command) },
[:stream, :binary, :exit_status, :hide])
do_cmd(port, callback)
end
defp do_cmd(port, callback) do
receive do
{ ^port, { :data, data } } ->
callback.(data)
do_cmd(port, callback)
{ ^port, { :exit_status, status } } ->
status
end
end
end
+10
View File
@@ -4,6 +4,16 @@ defmodule Mix.Shell.IO do
It simply prints messages to stdio and stderr.
"""
@behaviour Mix.Shell
@doc """
Executes the given command and prints its output
to stdout as it comes.
"""
def cmd(command) do
Mix.Shell.cmd(command, IO.write(&1))
end
@doc """
Writes a message to the shell followed by new line.
"""
+10
View File
@@ -36,6 +36,16 @@ defmodule Mix.Shell.Process do
end
end
@doc """
Executes the given command and fowards its messages to
the current process.
"""
def cmd(command) do
Mix.Shell.cmd(command, fn(data) ->
self <- { :mix_shell, :run, [data] }
end)
end
@doc """
Simply forwards the message to the current process.
"""
+11 -7
View File
@@ -64,8 +64,8 @@ defmodule Mix.Tasks.Deps.Compile do
File.cd! deps_path, fn ->
cond do
opts[:compile] -> do_compile(opts[:compile], app)
mix? -> do_mix(dep, config)
opts[:compile] -> do_compile app, opts[:compile]
mix? -> do_mix dep, config
rebar? -> do_command app, "rebar", "compile deps_dir=#{inspect root_path}"
make? -> do_command app, "make"
true -> shell.error "Could not compile #{app}, no mix.exs, rebar.config or Makefile " <>
@@ -109,22 +109,26 @@ defmodule Mix.Tasks.Deps.Compile do
defp do_command(app, command, extra // "") do
if System.find_executable(command) do
Mix.shell.info System.cmd("#{command} #{extra}")
if Mix.shell.cmd("#{command} #{extra}") != 0 do
raise Mix.Error, message: "could not compile dependency #{app}, #{command} command failed"
end
else
raise Mix.Error, message: "could not find executable #{command} to compile " <>
"dependency #{app}, please ensure #{command} is available"
end
end
defp do_compile(:noop, _) do
defp do_compile(_, :noop) do
:ok
end
defp do_compile(atom, app) when is_atom(atom) do
defp do_compile(app, atom) when is_atom(atom) do
apply Mix.Project.get!, atom, [app]
end
defp do_compile(command, _) do
Mix.shell.info System.cmd command
defp do_compile(app, command) do
if Mix.shell.cmd(command) != 0 do
raise Mix.Error, message: "could not compile dependency #{app}, custom #{command} command failed"
end
end
end
+4 -4
View File
@@ -195,10 +195,10 @@ defmodule Mix.Tasks.DepsGitTest do
Mix.Project.push GitErrorApp
in_fixture "no_mixfile", fn ->
Mix.Tasks.Deps.Get.run []
message = "* Getting git_repo [git: #{inspect fixture_path("not_git_repo")}]"
assert_received { :mix_shell, :info, [^message] }
assert_received { :mix_shell, :error, _ }
exception = assert_raise Mix.Error, fn ->
Mix.Tasks.Deps.Get.run []
end
assert exception.message =~ %r/command `git clone/
end
after
Mix.Project.pop