Skip to content

Commit

Permalink
Adjust mix nerves.system.shell for OTP 26
Browse files Browse the repository at this point in the history
OTP 26 made some big changes around serial interface and it breaks the
`mix nerves.system.shell` task. In the end, the main issue is that we
no longer have an easy way to take over the STDIN of the task to forward
it to a shell running in the system build directory.

Many attempts were made to recapture STDIN, but decided that for now we
need to move on. Instead, this adjusts the task to do _all_ the setup
for the build directory as before and outputs a warning to the user for
the command that can be manually run in order to get the same shell that
was previously started with `mix nerves.system.shell`

For local building, it's simply `cd path/to/build`. The more useful
command is for those using docker which is a fairly involved command.
  • Loading branch information
jjcarstens committed Jul 31, 2023
1 parent 67e830b commit e3b6c91
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 48 deletions.
4 changes: 4 additions & 0 deletions lib/mix/nerves/shell.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ defmodule Mix.Nerves.Shell do
"""
@spec open(String.t(), [String.t()]) :: no_return()
def open(command, initial_input \\ []) 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.
Process.unregister(:user)

# We need to get raw binary access to the stdout file descriptor
# so we can directly pass through control characters output by the command
stdout_port = Port.open({:fd, 0, 1}, [:binary, :eof, :stream, :out])
Expand Down
23 changes: 14 additions & 9 deletions lib/mix/tasks/nerves.system.shell.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ defmodule Mix.Tasks.Nerves.System.Shell do
@doc false
@spec run([String.t()]) :: :ok
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
Expand All @@ -70,11 +65,21 @@ defmodule Mix.Tasks.Nerves.System.Shell do

{build_runner, _opts} = pkg.build_runner

build_runner.system_shell(pkg)
{cmd, initial_input} = build_runner.system_shell(pkg)

if String.to_integer(System.otp_release()) < 26 do
# Legacy shell support
Mix.Nerves.Shell.open(cmd, initial_input)
else
Mix.Nerves.IO.shell_warn("shell start deprecated", """
OTP 26 made several changes to the serial interface handling. Unfortunately, this
a regression in preventing the Nerves tooling from starting a system sub-shell.
# Set :user back to the real one
Process.register(user, :user)
However, the build directory and all needed pieces have been created and can be
accessed by manually running the command below:
:ok
#{[IO.ANSI.reset(), cmd]}
""")
end
end
end
17 changes: 7 additions & 10 deletions lib/nerves/artifact/build_runners/docker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,17 @@ defmodule Nerves.Artifact.BuildRunners.Docker do
end

@doc """
Connect to a system configuration shell in a Docker container
Command and initial input for starting a system shell
"""
@spec system_shell(Nerves.Package.t()) :: :ok
@spec system_shell(Nerves.Package.t()) :: {cmd :: String.t(), initial_input :: [String.t()]}
def system_shell(pkg) do
_ = preflight(pkg)
{_, image} = config(pkg)
platform_config = pkg.config[:platform_config][:defconfig]
defconfig = Path.join("/nerves/env/#{pkg.app}", platform_config)

initial_input = [
"echo Updating build directory.",
"echo This will take a while if it is the first time...",
"/nerves/env/platform/create-build.sh #{defconfig} #{@working_dir} >/dev/null"
]
{:ok, pid} = Nerves.Utils.Stream.start_link(file: build_log_path())
stream = IO.stream(pid, :line)
create_build(pkg, stream)
Nerves.Utils.Stream.stop(pid)

mounts = Enum.join(mounts(pkg), " ")
ssh_agent = Enum.join(ssh_agent(), " ")
Expand All @@ -148,7 +145,7 @@ defmodule Nerves.Artifact.BuildRunners.Docker do

set_volume_permissions(pkg)

Mix.Nerves.Shell.open(cmd, initial_input)
{cmd, []}
end

defp preflight(pkg) do
Expand Down
28 changes: 3 additions & 25 deletions lib/nerves/artifact/build_runners/local.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ defmodule Nerves.Artifact.BuildRunners.Local do
"""
@behaviour Nerves.Artifact.BuildRunner

alias Nerves.Artifact

@doc """
Builds an artifact locally.
Expand Down Expand Up @@ -50,30 +48,10 @@ defmodule Nerves.Artifact.BuildRunners.Local do
end

@doc """
Connect to a system configuration sub-shell
Command and initial input for starting a system shell
"""
@spec system_shell(Nerves.Package.t()) :: :ok
@spec system_shell(Nerves.Package.t()) :: {cmd :: String.t(), initial_input :: [String.t()]}
def system_shell(pkg) do
dest = Artifact.build_path(pkg)

error = """
Could not find bash or sh executable.
Make sure one of them are in your $PATH environment variable
"""

shell = System.find_executable("bash") || System.find_executable("sh") || Mix.raise(error)

script = Path.join(Nerves.Env.package(:nerves_system_br).path, "create-build.sh")
platform_config = pkg.config[:platform_config][:defconfig]
defconfig = Path.join("#{pkg.path}", platform_config)

initial_input = [
"echo Updating build directory.",
"echo This will take a while if it is the first time...",
"#{script} #{defconfig} #{dest} >/dev/null",
"cd #{dest}"
]

Mix.Nerves.Shell.open(shell, initial_input)
pkg.platform.system_shell(pkg)
end
end
29 changes: 25 additions & 4 deletions lib/nerves/system/br.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,39 @@ defmodule Nerves.System.BR do
make_archive(type, pkg, toolchain, opts)
end

defp make(:linux, pkg, _toolchain, opts) do
System.delete_env("BINDIR")
dest = Artifact.build_path(pkg)
@doc """
Command and initial input for starting a system shell
"""
@spec system_shell(Nerves.Package.t()) :: {cmd :: String.t(), initial_input :: [String.t()]}
def system_shell(pkg) do
{:ok, pid} = Nerves.Utils.Stream.start_link(file: "build.log")
stream = IO.stream(pid, :line)
create_build(pkg, stream)
Nerves.Utils.Stream.stop(pid)
{"cd #{Artifact.build_path(pkg)}", []}
end

defp create_build(pkg, stream) do
dest = Artifact.build_path(pkg)
script = Path.join(Nerves.Env.package(:nerves_system_br).path, "create-build.sh")
platform_config = pkg.config[:platform_config][:defconfig]
defconfig = Path.join("#{pkg.path}", platform_config)
_ = shell(script, [defconfig, dest])

case shell(script, [defconfig, dest], stream: stream) do
{_, 0} -> :ok
_ -> Mix.raise("Failed to create build directory")
end
end

defp make(:linux, pkg, _toolchain, opts) do
System.delete_env("BINDIR")
dest = Artifact.build_path(pkg)

{:ok, pid} = Nerves.Utils.Stream.start_link(file: "build.log")
stream = IO.stream(pid, :line)

create_build(pkg, stream)

make_args = Keyword.get(opts, :make_args, [])

case shell("make", make_args, cd: dest, stream: stream) do
Expand Down

0 comments on commit e3b6c91

Please sign in to comment.