Upgrading to Nerves 0.5

What's New and What's Changed

In early March, the Nerves Project published its v0.5.0 release, marking another big step forward in usability and completeness of the platform. In this post, I’d like to describe the major changes to be aware of as you upgrade existing projects or create new ones using Nerves 0.5.

Refreshed Docs and Examples

If you’re new to Nerves or you’ve been using it for a long time and just never got around to it, you may want to check out the Getting Started guides and documentation available on Hexdocs and the example projects available on GitHub. These have all been reviewed and updated to match what’s available in Nerves 0.5, so it’s a great way to quickly see how everything works. In particular, the nerves-examples projects can be used as a reference if you get stuck trying to start a new project or feature, or when upgrading an existing project.

The Default Build Target is Now host

The biggest change in Nerves 0.5 is that projects now default to building with a target of host instead of always cross-compiling for different hardware. The reason for this change is that it is very difficult to test and debug your code on your host when it is cross-compiled for another target architeture. In order to support this change, we have added some additional structure to the mix.exs configuration file that gets generated by default for new Nerves projects. Let’s generate a new project and walk through the important sections of mix.exs.

$ mix nerves.new example

This @target module attribute is what controls the cross-compiling environment used by Mix. If the MIX_TARGET environment variable is set in your shell, it will be used to choose which Nerves System to use. If it is not set, Mix will default to the host target. If you’re familiar with previous versions of Nerves, you may also notice that we have changed the target-selection envirnoment variable from NERVES_TARGET to MIX_TARGET.

# example/mix.exs
defmodule Example.Mixfile do
  use Mix.Project

  @target System.get_env("MIX_TARGET") || "host"

Nerves 0.5 projects depend on version 0.3 of the nerves_bootstrap Mix archive. If you have a relatively recent version of nerves_bootstrap already, you can upgrade by running mix local.nerves. If that doesn’t work, you can run mix archive.install https://github.com/nerves-project/archives/raw/main/nerves_bootstrap.ez. Also note that the aliases() definition now takes the @target as a parameter. We’ll see what that does for us shortly.

  def project do
    [app: :example,
     # ...
     target: @target,
     archives: [nerves_bootstrap: "~> 0.3.0"],
     aliases: aliases(@target),
     deps: deps()]

Here, we set the Applications to be started, based on which @target is being used. This gives us the opportunity to automatically start the supervision tree when running on the target device, but not when running in iex on the host. Also note that we have switched to using the extra_applications convention for specifying which applications to include in the release. This will automatically include all applications in dependencies unless they are defined as runtime: false. Using extra_applications allows us to only list non-dependency applications (like Logger), rather than explicitly specifying them all in applications.

  def application, do: application(@target)

  def application("host") do
    [extra_applications: [:logger]]
  def application(_target) do
    [mod: {Example.Application, []},
     extra_applications: [:logger]]

Similarly, we choose which dependencies to include based on the @target selection. This gives an opportunity to easily swap out mock dependencies or simply not load any that are designed to run only on the target (e.g. to interface with specific hardware features).

Note that the nerves and nerves_system_* dependencies have now been marked as runtime: false so that they will not be included in the release on the device. These are only used during the compilation process to tell Mix how to properly build for the target hardware, so they aren’t needed at runtime. Instead, there is a new nerves_runtime dependency that contains only the components of Nerves that are usable from your running application. There isn’t much included today with nerves_runtime, but the intention of the Nerves team is to build out a standard platform of runtime components that many Nerves projects are likely to need.

  def deps do
    [{:nerves, "~> 0.5.0", runtime: false}] ++

  # Specify target specific dependencies
  def deps("host"), do: []
  def deps(target) do
    [{:nerves_runtime, "~> 0.1.0"},
     {:"nerves_system_#{target}", "~> 0.11.0", runtime: false}]

Finally, we use the @target attribute passed to the aliases() function to allow us to only inject the Nerves cross-compiling envinroment into Mix when we’re not targeting the host.

  # We do not invoke the Nerves Env when running on the Host
  def aliases("host"), do: []
  def aliases(_target) do
    ["deps.precompile": ["nerves.precompile", "deps.precompile"],
     "deps.loadpaths":  ["deps.loadpaths", "nerves.loadpaths"]]


Improved Console Interaction

One common complaint from previous versions of Nerves was that the console output was confusing during a Nerves firmware build. There were normal warnings that needed to be ignored and overly-verbose output from several tools without enough context. With Nerves 0.5, we have eliminated the noisy warnings and added high-contrast headers to outline the high-level build stages.

output from mix firmware

You may also notice that new Nerves projects are now generated with some helpful versions information being shown by default whenever you build you project. If you want to change what is displayed, you can easily modify the following code near the top of your mix.exs file.

  # example/mix.exs
  # ...
  Mix.shell.info([:green, """
    MIX_TARGET:   #{@target}
    MIX_ENV:      #{Mix.env}
  """, :reset])
  # ...

In order to help with troubleshooting, especially when using a custom Nerves System, we have also added a new mix nerves.info task, which shows more-detailed information:

output from mix nerves.info

Other Notable Changes

New images_path Option

You can now set the images_path option in your Mix.Project configuration. The default images_path location is #{build_path}/nerves/images, but you can override it if you want to place your built images elsewhere.

Better Project Generation by Default

Something that consistently tripped-up new users of Nerves in past versions was that they needed to run mix nerves.release.init and mix deps.get prior to mix firmware. In order to ease that process, the mix nerves.new project generator now asks if they would like to have it run these commands during project generation. This makes it much easier to do the right thing by default, only deviating if you know you need to.

output from mix nerves.new

comments powered by Disqus