lib/mix/tasks/nerves/system.shell.ex

defmodule Mix.Tasks.Nerves.System.Shell do
  use Mix.Task

  @shortdoc "Enter a shell to configure a custom system"

  @moduledoc """
  Open a shell in a system's build directory.

  In order to make the experience as similar as possible, we attach to a Docker
  container on non-Linux platforms and run a sub-shell on Linux.

  ## Examples

  Configure the system for the current project's target:

      mix nerves.system.shell

  """

  @standard_error_message """
  Make sure you run this task from a Nerves-based project or the source directory of a custom Nerves system.
  Also, set the MIX_TARGET environment variable to allow mix.exs to know which custom system dependency to use.
  """

  @no_nerves_dep_error """
  Nerves dependency not found in current Mix project.
  #{@standard_error_message}
  """

  @no_system_pkg_error """
  Unable to locate a relevant Nerves system package.
  #{@standard_error_message}
  """

  @no_mix_target_error """
  MIX_TARGET environment variable not set or set to "host".
  #{@standard_error_message}
  """

  @doc false
  def run(_argv) do
    # We unregister :user so that the process currently holding fd 0 (stdin)
    # can't send an error message to the console when we steal it.
    user = Process.whereis(:user)
    Process.unregister(:user)

    # Start disabled so that we can configure the system before building it
    # for the first time.
    try do
      Mix.Task.run("nerves.env", ["--disable"])
    rescue
      e ->
        case e do
          %Mix.Error{message: "Unknown dependency nerves for environment " <> _env} ->
            Mix.raise(@no_nerves_dep_error)

          _ ->
            reraise(e, __STACKTRACE__)
        end
    end

    pkg = Nerves.Env.system()

    if is_nil(pkg) do
      case Nerves.Bootstrap.mix_target() do
        :host -> Mix.raise(@no_mix_target_error)
        _ -> Mix.raise(@no_system_pkg_error)
      end
    end

    {build_runner, _opts} = pkg.build_runner

    build_runner.system_shell(pkg)

    # Set :user back to the real one
    Process.register(user, :user)
  end
end