Why?
Building embedded linux rootfs images using buildroot is (usually)
deterministic, but requires some host system setup to get working. While it
seems simple to tell people "just sudo apt install these packages, that ties
you to a specific distribution/version and there's no way to keep that package
list up-to-date.
Nix is able to provide a hermetic and bit-for-bit reproducible build environment with which to build buildroot. This gives everyone on your team (and CI!) the same build environment regardless of which distro they are running and what software they have installed: no more "works on my machine" as your team grows. Additionally, nix gives you the ability to create a shared remote cache so developers can automatically reuse builds without having to build a custom caching layer.
Why not use nixos?
Buildroot is more familiar to embedded teams and requires less ramp up for developers. Also, it is easier to integrate third-party or vendor code with buildroot than it is to integrate with nixos.
How do I set this up?
Add the following flake.nix to the root of your buildroot repo:
{
inputs.buildroot-nix.url = "github:velentr/buildroot.nix/master";
outputs = {
self,
nixpkgs,
buildroot-nix,
}: let
supportedSystems = ["x86_64-linux"];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in {
packages = forAllSystems (system: let
buildrootPackages = buildroot-nix.lib.mkBuildroot {
name = "my-buildroot";
pkgs = nixpkgs.legacyPackages.${system};
defconfig = "qemu_aarch64_virt_defconfig";
lockfile = ./buildroot.lock;
src = self;
};
in {
lockfile = buildrootPackages.packageLockFile;
default = buildrootPackages.buildroot;
});
};
}Make sure to set the appropriate defconfig for you hardware. Then, generate
the lockfile to ensure that future builds are deterministic:
nix build .#lockfile
cp -L result buildroot.lockThis step needs to be repeated as packages are added or updated to keep the lockfile up-to-date.
Once the lockfile is set up, you can build your buildroot images:
nix buildWhile nix gives you a hermetic build environment, you will not by default get a reproducible build. To check whether your build is deterministic:
nix build --rebuild --keep-failedThis will keep around both builds if the don't match and you can compare them with something like diffoscope to find the differences.
Here are a few buildroot options that can help make your build deterministic:
-
Set
BR2_REPRODUCIBLE=y. This one is pretty obvious and I'm not totally sure why it isn't the default (build time maybe?). -
If you are generating an ext2/3/4 image, it will contain a randomly-generated UUID. You can instead use a fixed UUID by passing in an option during filesystem creation. Use
uuidgento generate a UUID, then set it withBR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS="-U <your uuid here>"