Skip to content

7. Parent Integration

Austin Shirley edited this page Oct 22, 2026 · 4 revisions

dbl-buildroot is designed to be consumed as a git submodule by one or more downstream parent projects (also called "superprojects"). A superproject adds project-specific packages, configuration overlays, and CI on top of the base build system.

Architecture

parent-repo/                    (superproject)
├── modules/dbl-buildroot/      (submodule)
│   ├── lib.nix                 mkProject entry point
│   ├── inputs.nix              canonical input URLs for target revisions
│   ├── nix/build.nix           hermetic build derivation
│   └── support/parent/         integration scripts
├── overlay/                    (BR2_EXTERNAL tree)
│   ├── external.desc
│   ├── Config.in
│   └── my-project.fragment     (defconfig overlay)
├── flake.nix
├── Makefile
└── .pre-commit-config.yaml

The submodule provides the following:

  • lib.mkProject: Builds packages and devShells from a single call.
  • inputs.nix: Revision-pinned input URLs, fetched by lib.nix to be inherited by parent flakes.
  • support/parent.mk: Makefile fragment providing make build, make develop, make update-dbl-buildroot, make check-dbl-buildroot, and make nix-overlays.
  • support/parent/update.sh: One-shot bumper for the submodule and all SHA pins.
  • support/parent/check-pinned-shas.sh: Drift gate script.
  • .github/workflows/check-reusable.yml: Reusable submodule-check workflow.
  • .github/actions/setup-nix: Shared CI Nix-environment setup.
  • .github/actions/lockfile: Composite buildroot.lock/toolchain.lock drift gate action.
  • .github/actions/build: Composite build action.
  • .pre-commit-hooks.yaml: Exported hooks for downstream repositories.

Quick start

git init my-project && cd my-project
git submodule add git@github.com:deadbandlabs/dbl-buildroot.git modules/dbl-buildroot
modules/dbl-buildroot/support/parent/init.sh --name=my-project --board=myd-yf135

This generates everything from the template in support/parent/template/:

Generated file Purpose
flake.nix Nix flake calling mkProject with overlay and fragment
Makefile Includes support/parent.mk; forwards make to submodule, provides make build for Nix
overlay/ BR2_EXTERNAL tree (external.desc, Config.in, fragment, package/Config.in)
.pre-commit-config.yaml Hook IDs from submodule, SHA pinned
.github/workflows/build.yml Calls check-reusable.yml, then setup-nix plus the lockfile and build composite actions at pinned SHA
.envrc direnv integration
.gitignore Standard patterns
REUSE.toml License annotations

After init, review the generated files, stage, and verify:

git add -A
make check-dbl-buildroot
make build

The first make command verifies that drift gates pass. The second performs a hermetic Nix build. Run init.sh --help for all options (copyright, board, etc.).

Updating the submodule

make update-dbl-buildroot
make update-dbl-buildroot REF=v1.2.3

The first command advances the submodule to origin/main. The second advances to a specific tag.

This runs support/parent/update.sh, which:

  1. Fetches and checks out the target ref in the submodule.
  2. Propagates the new SHA to .pre-commit-config.yaml and all workflow uses: refs.
  3. Runs consistency checks.

Stage everything it reports at the end:

git add modules/dbl-buildroot .pre-commit-config.yaml .github/workflows/

Drift detection

One pre-commit hook catches drift of submodule references (SHAs) before it reaches CI:

Hook Script What it checks
dbl-buildroot-pinned-shas support/parent/check-pinned-shas.sh Submodule SHA vs. rev: in pre-commit config and uses:@<sha> in workflows

This is a no-op when running inside the submodule itself (repositories without a modules/dbl-buildroot/ directory).

Bumping the buildroot, nixpkgs, or buildroot-nix pins themselves is done by editing modules/dbl-buildroot/inputs.nix upstream and bumping the submodule pointer in the parent. There is no parent-side input to keep in sync.

mkProject parameters

Parameter Required Default Description
name yes - Project name (derivation name)
board yes - Board name; drives defconfig and flashLayout defaults
nixpkgs no self-fetched from inputs.nix Override nixpkgs source
buildroot no self-fetched from inputs.nix Override Buildroot source
buildroot-nix no self-fetched from inputs.nix Override buildroot.nix
defconfig no <board-with-underscores>_defconfig Override defconfig name
flashLayout no board/<board>/flashlayout.tsv Override flash layout path
extraExternalSrcs no [] Additional BR2_EXTERNAL trees
extraRootfsOverlays no ({ sdk, pkgs }: [ ]) Function returning extra rootfs overlay directories for Nix-built image content (see below)
configFragment no null Defconfig fragment to merge over base
configFragmentDebug no null Additional defconfig fragment merged on debug builds only
lockfile no submodule's buildroot.lock Project-local package lock (see below)
toolchainName no myd-yf135-toolchain Toolchain SDK derivation name; keep the default to substitute the CI-cached SDK
toolchainDefconfig no submodule's configs/myd_yf135_toolchain_defconfig Toolchain-only defconfig for the stage-1 SDK build
toolchainLockfile no submodule's toolchain.lock Toolchain SDK source lock
toolchainFragment no submodule's configs/myd_yf135_external_toolchain.fragment External-toolchain description merged into target builds
system no "x86_64-linux" Target system
extraDevShellPackages no [] Additional packages in development shell

Layered defconfig stacking

When a parent consumes the board layer through support/parent.mk, it can supply project-specific defconfig fragments that stack on top of the base configuration. The parent Makefile sets DBL_BR_FRAGMENT for the release fragment and, optionally, DBL_BR_FRAGMENT_DEBUG for an additional debug-only fragment. The merge proceeds in the following order:

base release defconfig
  + parent release fragment   (DBL_BR_FRAGMENT)
  + base debug fragment       (configs/myd_yf135_debug.fragment)
  + parent debug fragment     (DBL_BR_FRAGMENT_DEBUG, optional)

Release builds stop after the parent release fragment. Debug builds extend the stack with the base debug fragment and the optional parent debug fragment. Each later layer takes priority on conflicting keys, while keys matching *_FRAGMENT_FILES and BR2_ROOTFS_OVERLAY accumulate. Refer to the defconfig merger for the merge semantics.

A parent Makefile selects the fragments as follows:

DBL_BR_OVERLAY        := overlay
DBL_BR_FRAGMENT       := overlay/my-project.fragment
DBL_BR_FRAGMENT_DEBUG := overlay/my-project-debug.fragment   # optional
include modules/dbl-buildroot/support/parent.mk

The equivalent Nix build passes the same fragments to mkProject:

mkProject {
  name = "my-project";
  board = "myd-yf135";
  extraExternalSrcs   = [ ./overlay ];
  configFragment      = ./overlay/my-project.fragment;
  configFragmentDebug = ./overlay/my-project-debug.fragment;   # optional
}

The support/parent.mk forwarder injects BR2_EXTERNAL_EXTRA, CONFIG_FRAGMENT, and CONFIG_FRAGMENT_DEBUG into the submodule build automatically, so a plain make or make debug in the parent applies the correct stack without any further flags.

Parent device tree

A superproject building for its own board hardware may extend the base device trees. The parent may provide any of:

File (parent BR2_EXTERNAL tree) Extends
board/dts/parent-linux.dtsi Linux DT (stm32mp135d-myd-yf135.dts)
board/dts/parent-u-boot.dtsi U-Boot control DT (stm32mp135d-myd-yf135-u-boot.dtsi)
board/dts/parent-optee.dtsi OP-TEE DT (stm32mp135d-myd-yf135-optee.dts)
board/dts/parent-tf-a.dtsi TF-A BL2 DT (stm32mp135d-myd-yf135-tf-a.dtsi)
board/dts/parent-tf-a-fw-config.dtsi TF-A load addresses / TZC regions (stm32mp135d-myd-yf135-tf-a-fw-config.dts)

external.mk scans all consuming externals automatically following the above path convention (no hooks, defconfig keys, or Makefile variables are required in the parent).

Each base DT ends with an #include of a parent stub, so the parent is included after every base node and can override properties, /delete-node/, and add nodes. The dtb filename, DEVICE_TREE, and the U-Boot environment are kept, so RAUC bundles, the flash layout, and boot scripts are generally unaffected when a parent adopts its own DT.

Mechanism

  • Linux: the LINUX_MYD_YF135_COPY_DTS pre-build hook copies the empty stub into the kernel tree, then the parent's file over it. Like any Buildroot package source change, DT edits need a rebuild: make linux-rebuild (and make uboot-rebuild for parent-u-boot.dtsi) before re-running the image build.
  • U-Boot: the UBOOT_MYD_YF135_COPY_DTSI pre-build hook copies the parent's parent-u-boot.dtsi (or stub) into arch/arm/dts/, mirroring the Linux hook. U-Boot compiles the base Linux dts as its control DT source, so the hook also copies the empty base parent-linux.dtsi stub.
  • OP-TEE and TF-A: the OPTEE_OS_MYD_YF135_COPY_DTSI and ARM_TRUSTED_FIRMWARE_MYD_YF135_COPY_DTSI pre-build hooks copy the parent files (or the stubs) into core/arch/arm/dts/ and fdts/. OTP non-secure grants and TZC regions are security-critical configuration and should be modified with care.

Constraints

  • Parent dtsi files are included textually, not compiled standalone. They must not contain /dts-v1/ (dtc only accepts it at the top of the file), or #include the base DT (recursive include).
  • Keep the U-Boot parent dtsi minimal (gpio hogs, cd-gpios, and similar). The U-Boot control DT only needs what U-Boot itself uses; Linux peripheral additions (SPI, I2C, ADC, PWM) can cause U-Boot to probe additional drivers.
  • gpio-hog nodes are processed in board_r init. CONFIG_GPIO_HOG is already enabled in the base uboot.config, so hogs work without parent-side U-Boot config.
  • At most one external may provide each parent DT file. Composing them from multiple externals is not supported.

Nix-built rootfs overlays

Image content that is built by Nix instead of a Buildroot package (for example, a cross-compiled Rust binary) can be layered into the image as an extra rootfs overlay. The overlay directories are expressed as a generated defconfig fragment whose BR2_ROOTFS_OVERLAY value accumulates onto the base and parent overlays, so no defconfig editing is needed.

Hermetic builds

mkProject accepts extraRootfsOverlays, a function returning a list of overlay directories. It is passed the image's toolchain SDK and pkgs so binaries can be cross-built against the image libc:

mkProject {
  # ...
  extraRootfsOverlays = { sdk, pkgs }: [
    (pkgs.callPackage ./nix/my-app-overlay.nix { inherit sdk; })
  ];
}

Each entry is a directory laid out like a rootfs overlay (for example usr/bin/my-app inside it).

Interactive builds

For interactive submodule builds, the parent Makefile lists flake attributes that produce the same overlay directories:

DBL_BR_NIX_ROOTFS_OVERLAYS := my-app-overlay
include modules/dbl-buildroot/support/parent.mk

The make nix-overlays target builds each attribute and symlinks the result at output/nix-overlays/<attr>. The all, debug, and release targets depend on nix-overlays and stack the built store paths onto BR2_ROOTFS_OVERLAY automatically, so plain make picks up overlay changes.

Note that raw Buildroot image targets (for example make rootfs-ubifs) skip the nix-overlays prerequisite and reuse whatever BR2_ROOTFS_OVERLAY the last all/debug build left in .config. After changing the overlay configuration, run a full variant target before raw image targets.

Package lockfile

The build prefetches all Buildroot package sources from a buildroot.lock. The lockfile parameter defaults to the submodule's own lock, which only covers the base packages used by dbl-buildroot.

If your overlay adds packages (or bumps versions via configFragment), you must point lockfile at a project-local lock, or the build will fetch overlay sources at build time and may fail in the FHSEnv.

mkProject {
  # ...
  configFragment = ./overlay/my-project.fragment;
  lockfile = ./buildroot.lock;
}

Generate or refresh that lock from the merged (base plus overlay) config with:

make nix-lock

This writes ./buildroot.lock; commit the result. The make nix-lock command builds the #lockfile flake output, which enumerates the overlay-selected packages from the same externals and merged defconfig as the real build. Re-run and commit it whenever you change package versions or add packages.

In a parent, make nix-lock regenerates only the target lock. The toolchain SDK is reused from the submodule's cached build (substituted from Cachix when the toolchainName default is kept), so no parent-side toolchain.lock is needed. A parent that overrides the toolchain* parameters to build a custom toolchain must also generate a project-local lock from the #toolchain-lockfile flake output.

The generated build.yml calls the lockfile composite action, which regenerates the locks in CI and fails the build if a committed lock is stale or missing.

Local development

make develop
make build
make sdk
make nix-lock
make
make debug

The make develop command enters the Nix development shell. The make build command performs a hermetic release build. The make sdk command produces the SDK tarball. The make nix-lock command regenerates ./buildroot.lock after package changes. The plain make command starts an interactive Buildroot session (for debug, menuconfig, etc.). The make debug command builds the debug variant.

Clone this wiki locally