Improve shelling out on mix, closes #492
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user