diff --git a/lib/mix/lib/mix/cli.ex b/lib/mix/lib/mix/cli.ex index d74b4ff6df..5c7935ab78 100644 --- a/lib/mix/lib/mix/cli.ex +++ b/lib/mix/lib/mix/cli.ex @@ -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 \ No newline at end of file diff --git a/lib/mix/lib/mix/exceptions.ex b/lib/mix/lib/mix/exceptions.ex index 4ac5540f5e..10921688bb 100644 --- a/lib/mix/lib/mix/exceptions.ex +++ b/lib/mix/lib/mix/exceptions.ex @@ -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 diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex index b57ee9cf1c..aac6c9eaf1 100644 --- a/lib/mix/lib/mix/scm/git.ex +++ b/lib/mix/lib/mix/scm/git.ex @@ -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 \ No newline at end of file diff --git a/lib/mix/lib/mix/shell.ex b/lib/mix/lib/mix/shell.ex index 417526fefb..b851a00a83 100644 --- a/lib/mix/lib/mix/shell.ex +++ b/lib/mix/lib/mix/shell.ex @@ -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 \ No newline at end of file diff --git a/lib/mix/lib/mix/shell/io.ex b/lib/mix/lib/mix/shell/io.ex index f81bad493d..3fb129dfc6 100644 --- a/lib/mix/lib/mix/shell/io.ex +++ b/lib/mix/lib/mix/shell/io.ex @@ -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. """ diff --git a/lib/mix/lib/mix/shell/process.ex b/lib/mix/lib/mix/shell/process.ex index 6ee6696844..fd9a4cd904 100644 --- a/lib/mix/lib/mix/shell/process.ex +++ b/lib/mix/lib/mix/shell/process.ex @@ -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. """ diff --git a/lib/mix/lib/mix/tasks/deps.compile.ex b/lib/mix/lib/mix/tasks/deps.compile.ex index ab7b41c7db..d59a41aa68 100644 --- a/lib/mix/lib/mix/tasks/deps.compile.ex +++ b/lib/mix/lib/mix/tasks/deps.compile.ex @@ -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 \ No newline at end of file diff --git a/lib/mix/test/mix/tasks/deps.git_test.exs b/lib/mix/test/mix/tasks/deps.git_test.exs index a39e70ee44..7b0f86c6b7 100644 --- a/lib/mix/test/mix/tasks/deps.git_test.exs +++ b/lib/mix/test/mix/tasks/deps.git_test.exs @@ -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