Using Buildroot to Customize the Toolchain and Applications

Using Buildroot to Customize the Toolchain and Applications

1. Overview

The Emcraft uClinux BSP includes Buildroot -- a build system that generates a complete cross-compilation toolchain, root filesystem, and user-space packages for the ARM Cortex-M7 targets. The BSP ships with a pre-compiled toolchain in the tools/ directory, which is sufficient for most use cases. However, advanced users may need to:

  • Rebuild the toolchain with different configuration options.

  • Add third-party packages (e.g., networking utilities, scripting languages, additional libraries) to the root filesystem.

  • Customize the C library (uClibc-ng) or compiler (GCC) settings.

The Buildroot source tree is provided in the buildroot/ directory at the top level of the BSP distribution. Each BSP project can be independently configured to use either the pre-compiled toolchain from tools/ or a custom toolchain built from Buildroot.

This application note covers:

  • The Buildroot directory structure and default configuration.

  • How the Buildroot toolchain uses kernel headers from the BSP's Linux tree.

  • How to build the toolchain and SDK from Buildroot.

  • How to configure a BSP project to use the Buildroot-built toolchain.

  • How to add packages and include them in the root filesystem.

2. BSP Distribution Structure

The BSP top-level directory contains the following components relevant to Buildroot:

linux-cm-imxrt117x-3.3.2/ buildroot/ Buildroot source tree tools/ Pre-compiled toolchains projects/ BSP projects (rootfs, rootfs_flash, etc.) linux/ Linux kernel source u-boot/ U-Boot bootloader source A2F/ Third-party libraries (busybox, LVGL, etc.) ACTIVATE.sh Environment setup script

A single cross-compilation toolchain -- arm-buildroot-uclinuxfdpiceabi- (uClibc-ng FDPIC) -- drives the entire build: the Linux kernel, U-Boot, and userspace applications. Bare-metal targets (kernel, U-Boot armv7m) suppress the toolchain's FDPIC/PIE defaults with -mno-fdpic -fno-pic -no-pie; those flags are baked into the kernel's arch/arm/Makefile and U-Boot's arch/arm/cpu/armv7m/config.mk.

The pre-compiled toolchain in tools/ is a snapshot of the Buildroot SDK output. The buildroot/ directory contains the full source to rebuild it.

3. Buildroot Configurations

The Buildroot source tree includes two pre-defined configurations for the BSP:

Defconfig

Description

Defconfig

Description

arm_cortexm_sdk_lite_defconfig

Lightweight SDK – toolchain and the packages the stock rootfs project deploys

arm_cortexm_sdk_defconfig

Full SDK – the complete package set tested by Emcraft on the noMMU/FDPIC toolchain; also the source from which the A2F/root legacy-mode snapshot is regenerated

The arm_cortexm_sdk_lite_defconfig is recommended when you only need what the stock rootfs project deploys: busybox, GDB, i2c-tools, libubootenv, tslib, dropbear, and openssh-sftp-server. Anything beyond this set will be built but not included in the on-target image, only inflating the SDK.

The full arm_cortexm_sdk_defconfig is the complete package set Emcraft tests against the noMMU/FDPIC toolchain. It extends the lite set with libostree, aktualizr (Uptane OTA client), their dependencies (boost, libarchive, libsodium, sqlite, jsoncpp, libtirpc, openssl, libcurl, expat, util-linux, host-asn1c, urandom-scripts, bzip2, jq) – supporting an OSTree-based root filesystem with Uptane-compliant OTA updates as a primary feature. The full defconfig also serves a second purpose: its target/ tree is the source from which A2F/root is regenerated, so projects that opt out of Buildroot mode (see §6.2) still see a layout-compatible userspace.

Both configurations produce a toolchain with the following settings:

  • Architecture: ARM (32-bit), ARMv7-M (Cortex-M7)

  • Instruction set: Thumb-2 only

  • FPU: FPv4-D16

  • Binary format: FDPIC (position-independent executables for no-MMU)

  • C library: uClibc-ng 1.0.47 with FDPIC support

  • Compiler: GCC 11.3.0 with C++ support

  • Threading: NPTL

The Buildroot source tree also includes Emcraft-specific patches to several packages for no-MMU compatibility, including fork() to vfork() substitutions in e2fsprogs, libglib2, and others.

4. Kernel Headers Integration

The Buildroot toolchain requires Linux kernel headers to build the C library (uClibc-ng) and certain packages. Rather than downloading a separate copy of the kernel source, the BSP build system configures Buildroot to use the kernel headers directly from the BSP's linux/ directory.

This is implemented using Buildroot's LINUX_HEADERS_OVERRIDE_SRCDIR mechanism. When a project build is triggered, the build system automatically generates a local.mk file in the Buildroot output directory:

LINUX_HEADERS_OVERRIDE_SRCDIR = <BSP_ROOT>/linux

This tells Buildroot to skip downloading kernel source and instead use the local kernel tree. Buildroot rsyncs the source into its build directory and runs make headers_install to extract the sanitized userspace headers. The toolchain is then built against these headers, ensuring that the userspace headers always match the kernel version shipped with the BSP (v6.12.20).

This process is fully automatic – no manual configuration is required. The local.mk file is created before the first Buildroot build and does not need to be regenerated unless the Buildroot output directory is cleaned.

5. Building the Toolchain from Buildroot

5.1. Prerequisites

Source the BSP activation script. The selection of a Buildroot SDK is made here, as the second positional argument:

$ cd linux-cm-imxrt117x-3.3.2 $ source ACTIVATE.sh <MCU> <buildroot_defconfig>

For example:

$ source ACTIVATE.sh IMXRT117X_NXPEVK arm_cortexm_sdk_lite_defconfig

When the second argument is given, ACTIVATE.sh exports BUILDROOT_DEFCONFIG and points CROSS_COMPILE, CROSS_COMPILE_APPS, TOOLS_DIR, TOOLS_LIBS, and PATH at the Buildroot output (buildroot/output_<defconfig-stem>/host/). Sourcing ACTIVATE.sh without the second argument selects legacy mode (pre-compiled toolchain from tools/).

The resolved selection is persisted to .activate-defaults at the BSP root, so the next no-argument source ACTIVATE.sh re-applies the same MCU and Buildroot SDK without retyping them. See Installing and Activating Cross Development Environment for full details on activation.

5.2. Automatic Build

With a Buildroot SDK selected via ACTIVATE.sh, a normal make in any BSP project builds the SDK on demand. No project-Makefile edit is required.

$ source ACTIVATE.sh IMXRT117X_NXPEVK arm_cortexm_sdk_lite_defconfig $ cd projects/rootfs $ make

If the Buildroot toolchain has not been built yet, the build system detects this automatically and builds it before proceeding with the kernel and application build. The first build takes a significant amount of time depending on the host machine, as it compiles GCC, binutils, uClibc-ng, and all selected packages from source. Subsequent builds reuse the existing SDK; Buildroot's per-package stamps drive any incremental rebuilds.

5.3. Explicit Buildroot Build

To build the Buildroot toolchain explicitly (e.g., to trigger a rebuild or generate the SDK tarball), use:

$ source ACTIVATE.sh IMXRT117X_NXPEVK arm_cortexm_sdk_lite_defconfig $ cd projects/rootfs $ make buildroot

This command performs three steps:

  1. make $BUILDROOT_DEFCONFIG -- loads the Buildroot configuration selected via ACTIVATE.sh.

  2. make -- builds the complete toolchain, C library, and all enabled packages.

  3. make sdk -- packages the toolchain into a relocatable SDK tarball.

Each Buildroot defconfig gets its own output directory, derived from the defconfig name. For example:

Defconfig

Output directory

Defconfig

Output directory

arm_cortexm_sdk_lite_defconfig

buildroot/output_arm_cortexm_sdk_lite/

arm_cortexm_sdk_defconfig

buildroot/output_arm_cortexm_sdk/

Multiple SDK builds coexist on disk under their per-defconfig output directories. Switching SDKs is a matter of re-sourcing ACTIVATE.sh with a different second argument -- the previously built SDK stays around and the new one builds on first make.

5.4. Building Directly in the Buildroot Directory

Alternatively, you can build the toolchain directly in the Buildroot directory. Use the O= parameter to specify the output directory:

$ cd buildroot $ make O=output_arm_cortexm_sdk_lite arm_cortexm_sdk_lite_defconfig $ make O=output_arm_cortexm_sdk_lite $ make O=output_arm_cortexm_sdk_lite sdk

This is useful if you want to configure and build the toolchain before associating it with a specific BSP project. The O= directory name should match the convention used by the BSP build system: output_ followed by the defconfig name without the _defconfig suffix.

6. Configuring a BSP Project to Use Buildroot

6.1. Enabling the Buildroot Toolchain

To use the Buildroot-built toolchain for a project, source ACTIVATE.sh with the desired Buildroot defconfig as the second positional argument:

$ source ACTIVATE.sh <MCU> <buildroot_defconfig>

For example:

$ source ACTIVATE.sh IMXRT117X_NXPEVK arm_cortexm_sdk_lite_defconfig

The selection is persisted in .activate-defaults at the BSP root, so subsequent no-argument source ACTIVATE.sh invocations reproduce it. Project Makefiles carry no toolchain-selection logic of their own and the same project builds against any selected SDK without edits.

When BUILDROOT_DEFCONFIG is set in the environment (by ACTIVATE.sh), the build system points the following variables at the corresponding Buildroot output (e.g. output_arm_cortexm_sdk_lite for arm_cortexm_sdk_lite_defconfig):

Variable

Value

Variable

Value

CROSS_COMPILE

arm-buildroot-uclinuxfdpiceabi-

CROSS_COMPILE_APPS

arm-buildroot-uclinuxfdpiceabi-

TOOLS_DIR

buildroot/output_<name>/host/arm-buildroot-uclinuxfdpiceabi

TOOLS_LIBS

buildroot/output_<name>/host/arm-buildroot-uclinuxfdpiceabi/sysroot

BUILDROOT_OUTPUT

buildroot/output_<name>

BUILDROOT_TARGET

buildroot/output_<name>/target (legacy alias kept for backwards compatibility)

TARGET_ROOT

buildroot/output_<name>/target (canonical name; use this in new .initramfs entries)

PATH

Prepended with buildroot/output_<name>/host/bin

BUILDROOT_DEFCONFIG

Exported as-is so rfs-builder.py can gate .initramfs lines on it via ifdef/ifndef directives

All custom application Makefiles that use $(CROSS_COMPILE_APPS)gcc (or $(CROSS_COMPILE)gcc) will automatically pick up the Buildroot-built compiler.

6.2. Reverting to the Pre-compiled Toolchain

Source ACTIVATE.sh with only the MCU argument (and no Buildroot defconfig):

$ source ACTIVATE.sh IMXRT117X_NXPEVK

ACTIVATE.sh rewrites .activate-defaults to drop any persisted BUILDROOT_DEFCONFIG, unexports the variable from the current shell, and points CROSS_COMPILE, PATH, TOOLS_DIR, etc. at the toolchain in tools/. In this mode Rules.make exports TARGET_ROOT := $(INSTALL_ROOT)/A2F/root, so any .initramfs line written against ${TARGET_ROOT} continues to resolve to a working source – A2F/root mirrors the layout (sonames, directory structure, ABI) of the Buildroot target/ tree produced by the full arm_cortexm_sdk_defconfig (see §3). Packages not yet migrated to Buildroot (pppd, wpa_supplicant, libnl, bluetooth/obex, alsa, etc.) live in the same A2F/root tree and remain available regardless of mode.

6.3. Switching SDKs in an Already-Built Project

When a project is rebuilt under a different SDK selection than its previous build, leftover artefacts (kernel modules, custom-app objects, the assembled uImage) are stale – they were compiled against the previous sysroot. Linking them with a different sysroot's libraries silently produces a mixed image.

To catch this, each project records the active SDK in a .sdk_stamp file in the project directory (one line: legacy or buildroot:<defconfig>). On the next make, if the stamp disagrees with the currently selected SDK, the build hard-fails with a diagnostic and refuses to proceed:

ERROR: rootfs was last built against SDK 'buildroot:arm_cortexm_sdk_lite_defconfig', current activation selects SDK 'buildroot:arm_cortexm_sdk_defconfig'. Run 'make clean' before rebuilding.

Run make clean to clear the project's outputs (including the stamp) and re-make to build fresh against the new SDK. The Buildroot output directory is not cleaned -- previously built SDKs stay on disk and are reused if you switch back later.

7. Adding Packages in Buildroot

7.1. Selecting Packages

Buildroot includes over 2600 packages. To browse and select packages, use menuconfig from the project directory:

$ cd projects/rootfs $ make buildroot-menuconfig

This opens the Buildroot interactive configuration menu. Navigate to Target packages to browse available packages organized by category (audio, networking, libraries, etc.). Select the desired packages, save the configuration, and exit. The changes are written back to the project's defconfig automatically -- there is no separate savedefconfig step.

The next make in the project directory automatically detects that the defconfig has changed (via md5 checksum) and rebuilds Buildroot to include the newly selected packages:

$ make

Note on package compatibility. Only the packages included in the Emcraft-maintained defconfigs (arm_cortexm_sdk_lite_defconfig and arm_cortexm_sdk_defconfig) have been verified to build and run on the noMMU/FDPIC toolchain. Buildroot itself contains more than 2600 packages; we do not provide a warranty that any particular one will work out of the box on this target.

Common reasons a package may fail include:

  • Hard dependency on an MMU (uses fork() or functionality guarded by BR2_USE_MMU in its Config.in) -- such packages cannot be enabled in menuconfig at all, since Buildroot hides them for noMMU targets.

  • Autotools/CMake not recognizing the uclinuxfdpiceabi host triple -- the package's own configure.in or aclocal.m4 may reject the target as "unsupported", disable shared-library support, or fall into a legacy code path.

  • FDPIC ABI quirks -- some tools that rely on detailed ELF layout, ptrace register layouts, or dynamic-loader internals may build cleanly but misbehave at runtime.

Many additional packages do work without modification. If you need a package that is not in the maintained defconfigs, expect some integration effort. Contact Emcraft support if you hit a package you would like to see validated or patched upstream.

7.2. Including Buildroot Packages in the Root Filesystem

After building a package in Buildroot, its output binaries and libraries are placed in the Buildroot staging and target directories (paths shown for arm_cortexm_sdk_lite_defconfig):

  • Binaries: buildroot/output_arm_cortexm_sdk_lite/target/usr/bin/, .../usr/sbin/

  • Libraries: buildroot/output_arm_cortexm_sdk_lite/target/usr/lib/

  • Headers (for development): buildroot/output_arm_cortexm_sdk_lite/host/arm-buildroot-uclinuxfdpiceabi/sysroot/usr/include/

To include these in a BSP project's root filesystem, add appropriate entries to the project's .initramfs configuration file. The build system exports ${TARGET_ROOT} -- a single source-prefix variable that resolves to the Buildroot target directory when BUILDROOT_DEFCONFIG is set, or to $(INSTALL_ROOT)/A2F/root when it is unset. Use ${TARGET_ROOT} in .initramfs files so the same entry works in both modes (${BUILDROOT_TARGET} is also exported in Buildroot mode as a legacy alias, but new entries should use ${TARGET_ROOT}).

For example, to include the nanocom serial terminal (after enabling BR2_PACKAGE_NANOCOM via make buildroot-menuconfig):

# In rootfs.initramfs -- add Buildroot binaries using the ${TARGET_ROOT} alias dir /usr/bin 0755 0 0 file /usr/bin/nanocom ${TARGET_ROOT}/usr/bin/nanocom 0755 0 0

If the chosen package depends on shared libraries built by Buildroot, include those too with additional file entries pointing at ${TARGET_ROOT}/usr/lib/.

For the rare case where a binary's path differs between Buildroot and legacy mode (the only current example is busybox: ${TARGET_ROOT}/bin/busybox in Buildroot mode versus $(INSTALL_ROOT)/A2F/busybox/busybox in legacy mode), use the ifdef/ifndef BUILDROOT_DEFCONFIG directives that rfs-builder.py evaluates against the build-time environment:

ifdef BUILDROOT_DEFCONFIG file /bin/busybox ${TARGET_ROOT}/bin/busybox 755 0 0 ifndef BUILDROOT_DEFCONFIG file /bin/busybox ${INSTALL_ROOT}/A2F/busybox/busybox 755 0 0

The ${TARGET_ROOT} variable is resolved automatically by rfs-builder.py during the build; it is always defined, regardless of mode.

7.3. Linking Applications Against Buildroot Libraries

When BUILDROOT_DEFCONFIG is enabled, the Buildroot sysroot is exported via the TOOLS_LIBS variable. Application Makefiles can reference headers and libraries from this sysroot. For example, to link an application against a library built by Buildroot:

CC = $(CROSS_COMPILE_APPS)gcc CFLAGS = -Os -I$(TOOLS_LIBS)/usr/include LDFLAGS = -L$(TOOLS_LIBS)/usr/lib -lsomelib app: app.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)

8. Busybox Integration

When BUILDROOT_DEFCONFIG is set in the environment (by sourcing ACTIVATE.sh with a second argument), busybox is built through Buildroot's infrastructure instead of the standalone A2F/busybox build. The project's existing busybox configuration file (<sample>.busybox) is used automatically -- Buildroot applies it via the BUSYBOX_CONFIG_FILE override in local.mk.

The busybox source code is shared between both build paths. Buildroot uses the BSP's A2F/busybox/ source tree via the BUSYBOX_OVERRIDE_SRCDIR mechanism, so any Emcraft-specific patches in the busybox source are preserved.

The build system tracks changes to the busybox configuration using an md5 checksum. If the config file changes between builds, busybox is automatically rebuilt. A clean rebuild can be forced with make bclean.

The following busybox-related targets work in both modes (buildroot and legacy):

Target

Description

Target

Description

make bmenuconfig

Open busybox interactive configuration

make bclean

Clean busybox build

The busybox binary is included in the root filesystem from ${BUILDROOT_TARGET}/bin/busybox when using buildroot mode.

9. Available Build Targets

The following Buildroot-related make targets are available in any BSP project when BUILDROOT_DEFCONFIG is set in the environment (sourced via ACTIVATE.sh's second argument):

Target

Description

Target

Description

make buildroot

Load defconfig, build everything, and generate the SDK

make buildroot-menuconfig

Open Buildroot interactive configuration; saves changes back to the defconfig on exit

make buildroot-sdk

Re-generate the SDK tarball (after manual changes)

make buildroot-clean

Clean the Buildroot output directory

make bmenuconfig

Open busybox interactive configuration (via Buildroot)

make bclean

Clean busybox build (via Buildroot)

10. Saving Buildroot Configuration Changes

Changes made via make buildroot-menuconfig are saved back to the project's defconfig automatically on exit (equivalent to running savedefconfig and copying the result into buildroot/configs/). No manual save step is required.

To preserve the changes as a separate named defconfig instead of modifying the active one, copy the current defconfig under a new name:

$ cp buildroot/configs/arm_cortexm_sdk_lite_defconfig buildroot/configs/my_custom_defconfig

Then re-source ACTIVATE.sh with the new name as its second argument:

$ source ACTIVATE.sh IMXRT117X_NXPEVK my_custom_defconfig

The selection is persisted to .activate-defaults. Subsequent make buildroot-menuconfig invocations will modify my_custom_defconfig.