Skip to content

Testing/CI/Release For Embedded Targets #16

Closed
@posborne

Description

@posborne

Problem

Embedded targets typically do not target the same architectures that are readily available on CI services like Travis and developers machines, yet we would still like for CI to be able to build our software for alternative architectures and, ideally, run tests on that architecture.

As an example, let's consider another project I am a maintainer on, nix-rust/nix, which although not strictly focused on embedded is likely to be needed on many embedded systems (it is a dependency of mio and many other crates as it provides a set of safer APIs on top of libc which may not be present in std). For this project, we want to do the following:

  • Ensure the software builds correctly
  • Ensure the software built for passes all unit tests
  • (For Rust Applications) Generate working debug/release binaries

Matrixed with these additional things for each of the above:

  • With each major feature combination
  • On each supported target
  • On each support version of Rust

Project Case Studies

Currently, there are several projects that implement their own solutions to this problem (to varying degrees of success) and a few projects which exist to help aid developers who are seeking to build/test for several different platforms.

rust-lang/libc

The libc crate is built for and runs tests against a number of different targets including several which are not yet officially supported. The libc crate contains a CI Directory which provides an overview of the strategy it uses for doing cross-build/testing.

This boils down to the following (ignoring platforms like Windows/OSX that are not really relevant for embedded):

  • Triples are specified using the TARGET variable and the desired rust version is specified with the rust variable in the travis matrix. Linux is used for the host OS. E.g.
    - os: linux
      env: TARGET=arm-unknown-linux-gnueabihf
      rust: stable
    - os: linux
      env: TARGET=x86_64-unknown-openbsd QEMU=openbsd.qcow2
      rust: stable
      script: sh ci/run-docker.sh $TARGET
  • The ci/run-docker.sh script will do the following for $TARGET:
    • Runs docker build to create a docker image using the Dockerfile in ci/docker/$TARGET. E.g. https://github.com/rust-lang/libc/blob/master/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile. The docker images contain all non-rust dependencies that are required to build and test for $TARGET. Typically, this is gcc/libc (for the libc crate) and qemu-user.
    • Runs docker run to launch a new container from the previously built image and executes the run.sh script
    • With the run command, the rust sysroot (-v rustc --print sysroot:/rust:ro) is shared with the container at /rust
    • If the QEMU environment variable is specified, the QEMU image is fetched and and a bunch of rigamarole and setup is performed in order to set things up. Finally, QEMU is run and the output is parsed to determine success. The only emulated machine currently is x86_64.
    • QEMU- is automatically selected for specific targets (see https://github.com/rust-lang/libc/blob/master/ci/run.sh#L107) and QEMU is just used for running the libc-test binary (cross compilation of this binary for the target is done on the host.

Things work well, but there is a moderate amount of complexity. Effort that is done to improve testing for the libc crate do not directly help out other projects which might want the same enhancements.

Major contributors here have been @alexchrichton, @japaric, @semarie

nix-rust/nix

This nix-rust/nix crate is based off of the work done in libc in an earlier version and has diverged some since then. This work was originally done by yours truly.

  • Travis is used to perform testing for the various *nix platforms that are supported.
  • Cross target builds are specified as follows:
    - os: linux
      env: TARGET=aarch64-unknown-linux-gnu DOCKER_IMAGE=posborne/rust-cross:arm
      rust: 1.7.0
      sudo: true
    - os: linux
      env: TARGET=arm-unknown-linux-gnueabihf DOCKER_IMAGE=posborne/rust-cross:arm
      rust: 1.7.0
      sudo: true
    - os: linux
      env: TARGET=mips-unknown-linux-gnu DOCKER_IMAGE=posborne/rust-cross:mips
      rust: 1.7.0
      sudo: true
    - os: linux
  • At present, it is assumed that containers that will be used are published. No images are built as part of the travis build process itself
  • Rust and the sysroot for targets supported by the docker image are built into the target image itself rather than being mounted into the container as is the case with libc.
  • CI executes the run-docker.sh script which for cross-targets will in turn call run-docker.sh
  • This script will run (and pull) the specified docker container from dockerhub, executing the run.sh script in the container.
  • Cross compilation will be performed and, similar to libc, based on the Target we will either execute the tests directly or run them using QEMU.
  • Since nix has multiple test files (as will most projects other than libc), there are some hacks in order to attempt to figure out what those are so they can be run: https://github.com/nix-rust/nix/blob/master/ci/run.sh#L66

This work was done by @posborne based on libc.

japaric/smoke

I haven't looked at this one much but @japaric has some relevant experience. Appears to use some combination of Docker/QEMU for build/testing. Dockerfile appears to be monolithic.

Others?

Looking for feedback on other projects that are doing a non-trivial amount with doing cross-build/test within the Rust ecosystem. MCU targets would be appreciated in addition to the above projects which focus on targets with an OS.

Ecosystem Projects

rust-embedded/docker-rust-cross

The goal of this repository was to provide the Docker images (a common theme for this work) in order to perform cross-compilation and cross-test for non-host architectures. To date, I have not been diligent about keeping these images up-to-date with each Rust release.

The goal is to have a common repository of Docker images that can be reused across projects like libc/nix/others so each project doesn't need to go in the weeds on that front.

@posborne is the maintainer of this project.

japaric/rust-everywhere

Rust-everywhere seeks to make it easier to cross-compile crates for other targets and publish binaries. It does not appear to help out with running tests on foreign architectures, although this is something that is likely to be desirable for projects targeting other architectures.

Final Note

For many libraries that do not do FFI (especially libc/kernel FFI), the importance of actually running tests on the target architecture is probably not as strong. Within embedded, however, there seems to be a much greater chance that using APIs that may not already have nice interfaces is increased. As such, I feel this forum is still an appropriate place to discuss how we want to handle this problem moving forward.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions