Systems
Using a Nerves System
When you generate a new Nerves project using the mix nerves.new
task, you will
end up with something like the following in your mix.exs
configuration:
# ...
@target System.get_env("MIX_TARGET") || "host"
# ...
def deps do
[{:nerves, "~> 0.7", runtime: false}] ++
deps(@target)
end
# Specify target specific dependencies
def deps("host"), do: []
def deps(target) do
[ system(target),
# ...
]
end
def system("rpi"), do: {:nerves_system_rpi, ">= 0.0.0", runtime: false}
def system("rpi0"), do: {:nerves_system_rpi0, ">= 0.0.0", runtime: false}
# ...
def system(target), do: Mix.raise "Unknown MIX_TARGET: #{target}"
This allows Nerves to load one or more target-specific dependencies when a
MIX_TARGET
system environment variable is specified. The official
nerves_system-*
dependencies contain the standard Buildroot configuration for
the Nerves platform on a given hardware target and have a dependency on the
appropriate toolchain for that target. The system and toolchain also reference a
pre-compiled version of the relevant artifact so that Mix can simply download
them instead of having to compile them (which takes quite a while).
Anatomy of a Nerves System
Nerves system dependencies are a collection of configurations to be fed into the the system build platform. Currently, Nerves systems are all built using the Buildroot platform. The project structure of a Nerves system is as follows:
nerves_system_rpi3
├── LICENSE
├── README.md
├── VERSION
├── cmdline.txt
├── config.txt
├── fwup.conf
├── linux-4.4.defconfig
├── mix.exs
├── nerves.exs
├── nerves_defconfig
├── post-createfs.sh
└── rootfs-overlay
└── etc
└── erlinit.config
└── fw_env.config
The mix.exs
defines the toolchain and build platform, for example:
def project do
[ # ...
compilers: Mix.compilers ++ [:nerves_package],
aliases: ["deps.precompile": ["nerves.env", "deps.precompile"]]]
end
# ...
defp deps do
[
{:nerves, "~> 0.7", runtime: false },
{:nerves_system_br, "~> 0.13.3", runtime: false },
{:nerves_toolchain_arm_unknown_linux_gnueabihf, "~> 0.10.0", runtime: false}
]
end
# ...
defp package do
[ # ...
files: ["LICENSE", "mix.exs", "<other files>"],
licenses: ["Apache 2.0"],
links: %{"Github" => "https://github.com/nerves-project/nerves_system_rpi3"}]
end
Nerves systems have a few requirements in the mix file:
- The
compilers
must include:nerves_system
compiler afterMix.compilers
. - There must be a dependency for the toolchain and the build platform.
- The
package
must specify all the requiredfiles
so they are present when downloading from Hex.
Package Configuration
In addition to the mix.exs
file, Nerves packages read from a special
nerves.exs
configuration file in the root of the package directory. This file
contains configuration information that Nerves loads before any application or
dependency code is compiled. It is used to store metadata about a package. Here
is an example from the nerves.exs
file for nerves_system_rpi3
:
use Mix.Config
version =
Path.join(__DIR__, "VERSION")
|> File.read!
|> String.trim
pkg = :nerves_system_rpi3
config pkg, :nerves_env,
type: :system,
version: version,
compiler: :nerves_package,
artifact_url: [
"https://github.com/nerves-project/#{pkg}/releases/download/v#{version}/#{pkg}-v#{version}.tar.gz",
],
platform: Nerves.System.BR,
platform_config: [
defconfig: "nerves_defconfig",
],
checksum: [
"nerves_defconfig",
"rootfs-overlay",
"linux-4.4.defconfig",
"fwup.conf",
"cmdline.txt",
"config.txt",
"post-createfs.sh",
"VERSION"
]
There are a few required keys in this file:
type
: The type of Nerves Package.Options are:
system
,system_compiler
,system_platform
,system_package
,toolchain
,toolchain_compiler
,toolchain_platform
.artifact_url
: The URL(s) of cached assets.For official Nerves systems and toolchains, we upload the artifacts to GitHub Releases.
platform
: The build platform to use for the system or toolchain.platform_config
: Configuration options for the build platform.In this example, the
defconfig
option for theNerves.System.Platforms.BR
platform points to the Buildroot defconfig fragment file used to build the system.checksum
: The list of files for which checksums are calculated and stored in the artifact cache.This checksum is used to match the cached Nerves artifact on disk with its source files, so that it will be re-compiled instead of using the cache if the source files no longer match.
Customizing Your Own Nerves System
For some applications, the pre-built Nerves Systems won’t meet your needs. For example, you may want to include additional Linux packages or run on hardware that isn’t in the list of Nerves-supported targets yet. In order to make the build process consistent across host platforms, Nerves uses a Docker container behind the scenes to perform the build on non-Linux hosts. This makes it possible for the steps below to apply to whatever host platform you’re using for development, as long as you have Docker for Mac or Docker for Windows installed on those platforms.
While you could design a system from scratch, it is easiest to copy and modify an existing one, renaming it to distinguish it from the official release. For example, if you’re targeting a Raspberry Pi 3 board, do the following:
git clone https://github.com/nerves-project/nerves_system_rpi3.git custom_rpi3
The name of the system directory is up to you, but we will call it custom_rpi3
in this example. It’s recommended that you check your custom system into your
version control system before making changes. This makes it easier to merge in
upstream changes from the official systems later. For example, assuming you’re
using GitHub:
# After creating an empty custom_rpi3 repository in your GitHub account
cd custom_rpi3
git remote rename origin upstream
git remote add origin git@github.com:YourGitHubUserName/custom_rpi3.git
git push origin master
Next, tweak the metadata for your system so it won’t conflict with the official one and won’t try to download a cached artifact that doesn’t exist yet:
# custom_rpi3/nerves.exs
use Mix.Config
# =vvv= Update the package name and remove (or replace) the artifact_url list
pkg = :custom_rpi3
config pkg, :nerves_env,
type: :system,
version: version,
compiler: :nerves_package,
# artifact_url: [
# "...",
# ],
platform: Nerves.System.BR,
platform_config: [
defconfig: "nerves_defconfig"
],
# =^^^=
# ...
# custom_rpi3/mix.exs
# =vvv= Update the module and application names
defmodule CustomRpi3.Mixfile do
# ...
def project do
[app: :custom_rpi3,
version: @version,
# ...
end
# =^^^=
# ...
# =vvv= Update the maintainer and project information
defp package do
[maintainers: ["Your Name"],
# ...
links: %{"Github" => "https://github.com/YourGitHubUserName/custom_rpi3"}]
end
# =^^^=
end
Now that the custom system directory is prepared, you just need to point to it
from your project’s mix.exs
. In this example, we assume that your
custom_rpi3
system directory is in the same directory as your nerves firmware
project directory, like so:
~/projects
├── custom_rpi3
└── your_project
# your_project/mix.exs
# ...
def system("rpi3"), do: {:nerves_system_rpi3, ">= 0.0.0", runtime: false}
def system("custom_rpi3"), do: {:custom_rpi3, path: "../custom_rpi3", runtime: false}
def system(target), do: Mix.raise "Unknown MIX_TARGET: #{target}"
Set your MIX_TARGET
to refer to your custom system and build your firmware.
cd ~/projects/your_project
export MIX_TARGET=custom_rpi3
mix deps.get
mix firmware
This process will take quite a bit longer than a normal firmware build (15 to 30
minutes) the first time. When it finishes, you will have confirmed that you can
successfully build an equivalent of the official rpi3
system. After your
custom system has been built, you can modify your application and re-build
firmware normally. The custom system will only re-build if you make changes to
the system source project itself.
Package Configuration
Because Buildroot can only be used from Linux, Nerves provides an abstraction
layer called the Nerves system configuration shell that allows the same
procedure to be used on Linux and non-Linux development hosts by using a
Linux-based Docker container on non-Linux platforms. To access this environment,
run the mix nerves.system.shell
task, either from your project directory or
from the custom system source directory.
$ export MIX_TARGET=rpi3
$ mix deps.get
Mix environment
MIX_TARGET: rpi3
MIX_ENV: dev
Running dependency resolution...
Dependency resolution completed:
<-SNIP->
* Getting nerves (Hex package)
Checking package (https://repo.hex.pm/tarballs/nerves-0.7.0.tar)
<-SNIP->
$ mix nerves.system.shell
Mix environment
MIX_TARGET: custom_rpi3
MIX_ENV: dev
==> distillery
Compiling 19 files (.ex)
Generated distillery app
==> nerves
Compiling 25 files (.ex)
Generated nerves app
Preparing Nerves Shell
Creating build directory...
Cleaning up...
Nerves /nerves/build >
Once at the Nerves /nerves/build >
shell prompt, the workflow for customizing
a Nerves system is the same as when using Buildroot outside of Nerves, using
make menuconfig
and make savedefconfig
. Remember that this is effectively a
sub-shell on both Linux and non-Linux platforms, so when you’re finished
updating the configuration and optionally re-building the system “manually”, you
can get back to your normal shell by typing exit
or pressing CTRL+D
.
The main package configuration workflows are divided into three categories, depending on what you want to configure:
- Select base packages by running
make menuconfig
- Modify the Linux kernel and kernel modules with
make linux-menuconfig
- Enable more command line utilities using
make busybox-menuconfig
NOTE: You can build the system “manually” using
make
from inside the system configuration shell if you want to iterate quickly while trying out different changes. When you’re ready to try out the system in your project, exit the shell and havemix firmware
take care of the re-build for you from your project directory. Please be aware that Buildroot does not handle incremental compilation well, so it’s recommended that you always runmake clean
beforemake
unless you’re experienced with Buildroot and understand when you can skip themake clean
step.
When you quit from the menuconfig
interface, the changes are stored
temporarily. To save them back to your system source directory, follow the
appropriate steps below:
After
make menuconfig
:Run
make savedefconfig
to update thenerves_defconfig
in your System.After
make linux-menuconfig
:make linux-savedefconfig cp build/linux-x.y.z/defconfig <your system>/linux-x.y_defconfig
If your system doesn’t contain a custom Linux configuration yet, you’ll need to update the Buildroot configuration (using
make menuconfig
) to point to the new Linux defconfig in your system directory. The path is usually something like$(NERVES_DEFCONFIG_DIR)/linux-x.y_defconfig
.After
make busybox-menuconfig
:make busybox-savedefconfig cp build/busybox-x.y.z/defconfig <your system>/busybox-x.y_defconfig
Like the Linux configuration, the Buildroot configuration will need to be updated to point to the custom config if it isn’t already.
The Buildroot user manual can be very helpful, especially if you need to add a package. The various Nerves system repositories have examples of many common use cases, so check them out as well.