Talking Tock 57
Cargo Build System for the Kernel
by BradWith pull request #4075 merged, Tock
now supports using standard cargo commands directly when compiling the kernel
for any Tock board. For example, you can now build the kernel for the nRF52840dk
by simply:
$ cd tock/boards/nordic/nrf52840dk
$ cargo build
Other commands, such as cargo test or cargo clippy, as well as plugins, such
as cargo bloat, should all work as they would in any Rust project as well.
Supporting Cargo in Tock
The main challenge with using cargo directly for Tock is the series of
specific build flags we use when running rustc. Our Makefile infrastructure
was designed to help set all of those flags. To replace that mechanism, we are
now using .cargo/config.toml files for each board. These configuration files
can specify the same flags we previously set in the Makefile.
The primary setting that must be set per-board is the compilation target. As
such, each board’s .cargo/config.toml file primarily looks like:
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.
[build]
target = "thumbv7em-none-eabi"
The other flags we need (for example setting the relocation model with -C
relocation-model=static) we do not want to include in the board’s
.cargo/config.toml file as we would have to copy them for each board. Instead,
we store additional configuration in the boards/cargo directory in a series of
.toml files. Each board can then choose which config files are appropriate
(for example, we include some flags on RISC-V boards but not on Cortex-M
boards). We accomplish this using the
config-include
feature.
For the nRF52840dk, the full .cargo/config.toml file looks like:
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.
include = [
"../../../cargo/tock_flags.toml",
"../../../cargo/unstable_flags.toml",
]
[build]
target = "thumbv7em-none-eabi"
[unstable]
config-include = true
Nightly Features
This approach does use three cargo features which are currently only available on the nightly version of cargo.
-
config-include: This feature allows us to select specificconfig.tomlfiles to include on a per-board basis. -
trim-paths: This feature re-writes file paths that are stored in the resulting binary to remove host-specific path names and help with reproducible builds. -
cargo config get: This feature retrieves the current active configuration and all settings.
To compile a board with the stable version of Rust these features have to be
avoided. The boards/hail board is an example of compiling on stable Rust.
The Role of Make
This change does not completely remove Make and Makefiles from building Tock boards. However, the role of Make has shifted. It now serves two primary goals:
- Helping with initial setup and ensuring the user has the correct tools installed (e.g., rustup, the Rust target, etc.). It also can print helpful debugging information.
- Creating
.binfiles and flashing the board. The Makefile has targets for runningobjcopyandobjdump.
In addition, running make still works as before to build the kernel.
Out-of-tree Boards
This change should help simplify maintaining an out-of-tree Tock board. Rather
than needing a copy of the Makefile.common file, boards can now simply point
to the boards/cargo/*.toml configuration files and use cargo build directly.
Drawbacks to Using Cargo
Despite Cargo being the defacto build system for Rust, making this change in Tock still required two attempts at a PR (#4054 and #4075) and 182 discussion posts on Github. The main reason for that is Cargo is still has limitations that make it not the ideal match for Tock. Ultimately, the benefits outweigh the disadvantages, however, we still document here the limitations with using Cargo.
-
Build configuration files must be stored in hidden folders. Cargo requires that build configurations files are stored in
.cargo/config.tomlfiles, which mean they are largely hidden from the user. With an embedded operating system, we rely on build configuration being set correctly, and this build information is just as critical to correctly building the OS as the source code is. Because of this, we have resisted using any hidden files in our build system. Hidden files are sometimes not searched by default and can be excluded from copy operations, making the Tock build system more difficult to inspect. Also, having the files be hidden implies they are optional or do not need to be modified per-board, which is incorrect.We mitigate this issue by making the
.cargo/config.tomlfile in each board as minimal as possible and putting all of the configuration flags (except for setting thetarget) in.tomlfiles that are inboards/cargo. Each board’sconfig.tomlis mostly just pulling in configuration files which are both well documented and not hidden. -
Command-line
RUSTFLAGSreplace flags set in Cargo configurations. If the user setsRUSTFLAGSmanually as an environment variable, Cargo will not include any of therustflagsset in the configuration.tomlfiles. That this is a replace operation rather than an append operation is often surprising to developers more familiar with C- and Make-style build systems which typically append flags.This behavior is potentially problematic for Tock for two reasons. 1) Errantly setting the
RUSTFLAGSenvironment variable on the command line causes all of Tock’s built-in configuration to be ignored. A user may not even realize this has happened. Or, a user may be following a guide which suggests settingRUSTFLAGSnot realizing that it disables all otherrustflags. This might create surprising behavior that would be very challenging to debug for new users who are not familiar with cargo and Tock’s build system. 2) It makes it difficult to simply append a new flag (say during development) while keeping the existing flags intact.To help mitigate these downsides, we add a sentinel flag called
cfg_tock_buildflagssentinelin our main.tomlfile. Then, inbuild.rswe check that the flag is in fact set. If it isn’t then something is amiss about the build flags being used and we print an error to the user. For users who want to override the build flags, then can simply set the sentinel flag as well.