Make Build System
Currently, NuttX supports both CMake and Make build systems.
This guide explains the NuttX make-based build system.
Due to requirements, constraints, and the complexity of the build process, NuttX divides this work into multiple files, each handling specific parts of the build process.
As stated in The Inviolable Principles of NuttX, multiple platforms should be supported:
NuttX supports multiple build modes. See NuttX Protected Build:
FlatLibs.mk: Kernel and user-space built into a single
blob.ProtectedLibs.mk: Kernel and user-space built as two separate
blobs.KernelLibs.mk: Kernel built into single
blob. User apps must be loaded into memory for execution.
NuttX targets multiple libs, or silos, each handling its own compilation:
Note
Gregory Nutt has a nice presentation about NuttX architecture
There the silo concept is explained. Only the silos there are listed below as libs.
The build mode influences the needed libs.
$ ls -l staging/
drwxr-xr-x 2 xxx xxx 4096 Oct 6 16:02 .
drwxr-xr-x 27 xxx xxx 4096 Oct 6 16:02 ..
-rw-r--r-- 1 xxx xxx 323640 Oct 6 16:02 libapps.a
-rw-r--r-- 1 xxx xxx 384352 Oct 6 16:02 libarch.a
-rw-r--r-- 1 xxx xxx 62182 Oct 6 16:02 libbinfmt.a
-rw-r--r-- 1 xxx xxx 6468 Oct 6 16:01 libboards.a
-rw-r--r-- 1 xxx xxx 2820054 Oct 6 16:02 libc.a
-rw-r--r-- 1 xxx xxx 161486 Oct 6 16:01 libdrivers.a
-rw-r--r-- 1 xxx xxx 981638 Oct 6 16:02 libfs.a
-rw-r--r-- 1 xxx xxx 224446 Oct 6 16:02 libmm.a
-rw-r--r-- 1 xxx xxx 2435746 Oct 6 16:01 libsched.a
-rw-r--r-- 1 xxx xxx 51768 Oct 6 16:02 libxx.a
Verbosity
The V variable can be passed to make to control the build verbosity.
Quiet (Default): The build output is minimal.
Verbose (`V=1 V=2`): Shows the full compiler commands (enables command echo).
Verbose Tools (`V=2`): Enables verbose output for tools and scripts.
# V=1,2: Enable echo of commands
$ make V=1
# V=2: Enable bug/verbose options in tools and scripts
$ make V=2
Build Process
Note
Keep the configuration step and board folder layout short. Separate docs should be created.
The Make based build process starts with the NuttX tree configuration.
This is done by running tools/configure.sh script.
The configuration step, prepares the NuttX kernel tree, setting the board specific
arch, chip and board files.
The Make build system refers the needed subsystems using generic naming:
The current architecture is refered as
archThe current chip is refered as
chipThe current board is refered as
board
These generic names are mapped to the actual names by symlinks:
The current chip directory gets symlinked to
nutt/include/arch/chip.The current board directory gets symlinked to
nutt/include/arch/board.The current arch directory gets symlinked to
nutt/include/arch.
The board config is stored as defconfig file, which is a minimal config,
storing only the configs that differs from default config values.
Due to NuttX’s particularity of strict dependance to the app
directory, the .config file is not generated by either kconfiglib or
kconfig-frontends, but rather by the an in-tree tools/process_config.sh
script. This script takes a “base” input file (the boards defconfig file),
additional include paths (the most relevant being the apps top directory),
and generate an output file (the $(TOPDIR)/.config file).
# part of configure.sh shell script, starting at line 240
#
# src_config=${configpath}/defconfig
# dest_config="${TOPDIR}/.config"
# original_config="${TOPDIR}/.config.orig"
# backup_config="${TOPDIR}/defconfig"
$ ln -sf ${src_makedefs} ${dest_makedefs} || \
{ echo "Failed to symlink ${src_makedefs}" ; exit 8 ; }
$ ${TOPDIR}/tools/process_config.sh -I ${configpath}/../../common/configs \
-I ${configpath}/../common \
-I ${configpath} -I ${TOPDIR}/../apps \
-I ${TOPDIR}/../nuttx-apps \
-o ${dest_config} ${src_config}
Starting the Build
The root Makefile is the build process entrypoint. Its main job is
to check for a .config file and include the appropriate host-specific Makefile.
The “actual” first steps of the build process are handled by the host-specific
Makefile (either Win.mk or Unix.mk).
Early on, during parsing, both host-specific Makefile will also include
board’s
Make.defsfile mentioned above. This will include alsothe main
.configfile.the tools
config.mkfile.the arch
toolchain.defsfile.
based on the build mode, one of the following files:
Built-in dependency mechanism
Note
The documentation for mkdeps.c, cnvwindeps.c, mkwindeps.sh, and mknulldeps.sh should be extended. Lots of details are missing. Part of the documentation here should be moved.
NuttX implements a built-in dependency mechanism. See mkdeps.c, cnvwindeps.c, mkwindeps.sh, and mknulldeps.sh.
This mechanism uses gcc to generate a *.d like dependency files that can be
included by the Makefile to track file dependencies.
This mechanism is kick-started by the depend target, and targets specific
directories in the NuttX tree based on the build mode. It is required by both
pass1dep and pass2dep.
pass1dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
$(Q) for dir in $(USERDEPDIRS) ; do \
$(MAKE) -C $$dir depend || exit; \
done
pass2dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
$(Q) for dir in $(KERNDEPDIRS) ; do \
$(MAKE) -C $$dir EXTRAFLAGS="$(KDEFINE) $(EXTRAFLAGS)" depend || exit; \
done
Both pass1dep and pass2dep sets different directories and orders
in which the depend target is ran. See Directories.mk.
Building NuttX libs
The host-specific Makefile will not build the required NuttX libs.
It will defer work by “recursively” calling make in each of the directories
listed in the $(USERLIBS) and $(NUTTXLIBS) variables.
pass1 & pass2
The NuttX binary is always generated by running both pass1 and pass2 targets.
The actual dependencies of the mentioned targets may vary depending on the build mode.
Different NuttX build modes will not influence the “execution” of the pass1 and pass2 targets,
but rather will influence the dependencies pulled by those targets.
pass1target depends on the$(USERLIBS).pass2target depends on the$(NUTTXLIBS).
all: pass1 pass2
pass1: $(USERLIBS)
pass2: $(NUTTXLIBS)
The content of the $(USERLIBS) and $(NUTTXLIBS) variables is defined in each build mode makefile.
See Build Modes above.
Staging the libs
After compiling libraries defined by the $(USERLIBS) and $(NUTTXLIBS) at the previous step,
the make build system will install them at a special staging/ directory, residing at the
root of the NuttX tree.
These libraries are passed to the the final target rule ($(BIN)).
# Unix.mk : line 536
$(BIN): pass1 pass2
# ...
$(Q) $(MAKE) -C $(ARCH_SRC) EXTRA_OBJS="$(EXTRA_OBJS)" LINKLIBS="$(LINKLIBS)" APPDIR="$(APPDIR)" EXTRAFLAGS="$(KDEFINE) $(EXTRAFLAGS)" $(BIN)
# ...
Win.mk
Although targeting different platforms, both Win.mk and Unix.mk aim to produce the same output. The need for independent files is due to the differences in the platform’s approaches.
Forward vs Back slashes
One of the main differences is the use of forward slashes
(/) on unix-like platforms versus backslashes (\) on windows
${HOSTEXEEXT} ${HOSTDYNEXT}
These variables are used by the build system to configure the executable suffix required by the used platform. They are defined in Config.mk.
For windows platform:
${HOSTEXEEXT}is set to.exe.${HOSTDYNEXT}is set to.dll.
Symbolic Linking
For the windows platform, the build system handles symbolic links differently.
Unix.mk
Versioning
The build system will impact versioning if NuttX is cloned as a repo. See Versioning.
config.h, .config, mkconfig
NuttX’s build system defers the config.h generation to a separate tool called
mkconfig. See Makefile.host.
tools/mkconfig$(HOSTEXEEXT): prebuild
$(Q) $(MAKE) -C tools -f Makefile.host mkconfig$(HOSTEXEEXT)
The include/nuttx/config.h recipe calls the mkconfig executable generated by the
rule above to create the config.h file from the current .config file.
Symlinks & dirlinks
Dirlinks are symbolic links that allow the build system to use generic paths while pointing to architecture-specific, chip-specific, or board-specific directories. This enables a single build system workflow across many different hardware configurations.
Symlink
arch/<arch-name>/includetoinclude/archSymlink
boards/<arch>/<chip>/<board>/includetoinclude/arch/boardSymlink
arch/<arch-name>/include/<chip-name>toinclude/arch/chipSymlink
boards/<arch>/<chip>/<board>toarch/<arch-name>/src/board/<board>
Note
Some boards make use of a common directory. In that case:
boards/<arch>/<chip>/commonis symlinked toarch/<arch-name>/src/boardboards/<arch>/<chip>/<board>is symlinked toarch/<arch-name>/src/board/<board>
Symlink
arch/<arch-name>/src/<chip-name>toarch/<arch-name>/src/chip
The .dirlinks file itself is just a timestamp marker that indicates all dirlinks have been
created.
Dummies
The main reason for the use of dummies is to handle some specific scenarios, such as external code bases, custom chips and boards or to overcome tooling limitations. If any of the features below are not used, the build system will fallback to a dummy.
${EXTERNALDIR}
Possible values for
$(EXTERNALDIR)areexternalordummy.NuttX code base can be extended by using
$(TOPDIR)/external/directory. The build system searches for aKconfigfile in that directory. If found, the build system defines theEXTERNALDIRvariable toexternaland also appends another lib (libexternal) to the build process.# External code support # If external/ contains a Kconfig, we define the EXTERNALDIR variable to 'external' # so that main Kconfig can find it. Otherwise, we redirect it to a dummy Kconfig # This is due to kconfig inability to do conditional inclusion. EXTERNALDIR := $(shell if [ -r $(TOPDIR)/external/Kconfig ]; then echo 'external'; else echo 'dummy'; fi)
dummy/Kconfig
The
dummy/Kconfigis used to handle custom chips and boards.If in-tree chip/board is used, the build system will resolve to dummy_kconfig files. -
$(CHIP_KCONFIG)is set to$(TOPDIR)$(DELIM)arch$(DELIM)dummy$(DELIM)dummy_kconfig-$(BOARD_KCONFIG)is set to$(TOPDIR)$(DELIM)boards$(DELIM)dummy$(DELIM)dummy_kconfigIf custom chip/board is used, the build system will resolve to their custom paths.
# Copy $(CHIP_KCONFIG) to arch/dummy/Kconfig arch/dummy/Kconfig: @echo "CP: $@ to $(CHIP_KCONFIG)" $(Q) cp -f $(CHIP_KCONFIG) $@ # Copy $(BOARD_KCONFIG) to boards/dummy/Kconfig boards/dummy/Kconfig: @echo "CP: $@ to $(BOARD_KCONFIG)" $(Q) cp -f $(BOARD_KCONFIG) $@
boards/dummy.c
A special
boards/dummy.cfile is used by the build system to generate a useless object. The purpose of the useless object is to assure that libboards.a/lib is created. Some archivers (ZDS-II, SDCC) require a non-empty library or they will generate errors.
Build Modes
As specified above, NuttX supports multiple build modes. The build mode is selected
based on specific Kconfig options.
# Library build selections
#
# NUTTXLIBS is the list of NuttX libraries that is passed to the
# processor-specific Makefile to build the final NuttX target.
# USERLIBS is the list of libraries used to build the final user-space
# application
# EXPORTLIBS is the list of libraries that should be exported by
# 'make export' is
ifeq ($(CONFIG_BUILD_PROTECTED),y)
include tools/ProtectedLibs.mk
else ifeq ($(CONFIG_BUILD_KERNEL),y)
include tools/KernelLibs.mk
else
include tools/FlatLibs.mk
endif
The content of each file referenced above is documented in its own section.
tools/ProtectedLibs.mk ProtectedLibs.mk
tools/FlatLibs.mk FlatLibs.mk
tools/KernelLibs.mk KernelLibs.mk
Config.mk
Config.mk contains common definitions used by many configuration files.
It defines the logic behind the Verbosity
It resolves the platform specific particularities, such as
build tools
e.g. mkdeps, cnvwindeps, link.sh/.bat, unlink.sh/.batfile extensions
e.g. .exe, .dll for windows, .o, .a for unixdelim based on the host platform.
e.g. \ for windows, / for unix
Resolve custom bard/chip/arch
Defines the rules for the dependency mechanism
OBJPATH ?= . %.dds: %.S $(Q) $(MKDEP) --obj-path $(OBJPATH) --obj-suffix $(OBJEXT) $(DEPPATH) "$(CC)" -- $(CFLAGS) -- $< > $@ %.ddc: %.c $(Q) $(MKDEP) --obj-path $(OBJPATH) --obj-suffix $(OBJEXT) $(DEPPATH) "$(CC)" -- $(CFLAGS) -- $< > $@ %.ddp: %.cpp $(Q) $(MKDEP) --obj-path $(OBJPATH) --obj-suffix $(OBJEXT) $(DEPPATH) "$(CXX)" -- $(CXXFLAGS) -- $< > $@ %.ddx: %.cxx $(Q) $(MKDEP) --obj-path $(OBJPATH) --obj-suffix $(OBJEXT) $(DEPPATH) "$(CXX)" -- $(CXXFLAGS) -- $< > $@ %.ddh: %.c $(Q) $(MKDEP) --obj-path $(OBJPATH) --obj-suffix $(OBJEXT) $(DEPPATH) "$(CC)" -- $(HOSTCFLAGS) -- $< > $@
Defines the include flag prefix based on compiler
Defines the common functions used to compile, assemble, archive files, etc.
This file (along with <nuttx>/.config) must be included at the top of each configuration-specific Make.defs file like
include $(TOPDIR)/.config
include $(TOPDIR)/tools/Config.mk
Subsequent logic within the configuration-specific Make.defs file may then override these default definitions as necessary.
FlatLibs.mk
This file defines the library sets for the flat build mode. In this mode, the NuttX kernel and all user-space applications are compiled and linked into a single, monolithic binary.
Its primary responsibilities are:
Populating ``NUTTXLIBS``: The file systematically appends all required libraries to the
NUTTXLIBSvariable. This includes core OS libraries (libsched,libmm,libc), architecture and board support libraries, and device drivers.Conditional Library Inclusion: It uses
ifeq ($(CONFIG_XXX),y)checks to conditionally include libraries for optional features based on the system’s Kconfig. For example,staging/libnet.ais added only ifCONFIG_NET=y, andstaging/libcrypto.ais added ifCONFIG_CRYPTO=y.Defining ``USERLIBS``: In the flat build, there is no separate user-space binary, so the
USERLIBSvariable is initialized but remains empty.Setting ``EXPORTLIBS``: It assigns the complete list of
NUTTXLIBSto theEXPORTLIBSvariable. This ensures that themake exportcommand packages all the compiled libraries required to link the final binary.
ProtectedLibs.mk
This file defines the library sets for the protected build mode. In this mode, the NuttX kernel and user-space applications are built as two distinct binaries, enabling memory protection and a more robust system architecture.
Its primary responsibilities are:
Separate Library Lists: Unlike the flat build, this file populates both the
NUTTXLIBSvariable (for the kernel binary) and theUSERLIBSvariable (for user-space applications).Kernel vs. User Library Variants: It explicitly differentiates between kernel and user variants of core libraries. For instance:
Kernel-specific libraries (e.g.,
libkc.a,libkmm.a,libkarch.a) are added toNUTTXLIBS.User-space counterparts (e.g.,
libc.a,libmm.a,libarch.a) are added toUSERLIBS.
System Call Mechanism: It includes essential components for the system call interface:
staging/libstubs.a(system call stubs) is added toNUTTXLIBS.staging/libproxies.a(system call proxies) is added toUSERLIBS. These facilitate safe communication between user applications and the kernel.
Conditional Library Inclusion: Similar to other build modes, it uses
ifeq ($(CONFIG_XXX),y)checks to conditionally include libraries for optional features in both kernel and user-space, based on the Kconfig settings.Exporting User Libraries: A key distinction is that
EXPORTLIBSis set to$(USERLIBS). This means that themake exportcommand will primarily package the user-space libraries, which are then used by external build systems or for linking user applications.
KernelLibs.mk
This file defines the library sets for the kernel build mode. In this mode, only the NuttX kernel is compiled and linked into a single binary. User applications are expected to be loaded and executed separately, typically in a protected user-space environment.
Its primary responsibilities are:
Kernel-Only ``NUTTXLIBS``: The file populates the
NUTTXLIBSvariable with all the necessary kernel-space libraries. This includes core OS components (e.g.,libsched,libdrivers), kernel variants of common libraries (e.g.,libkc.a,libkmm.a,libkarch.a), and board support.Empty ``USERLIBS``: The
USERLIBSvariable is explicitly initialized but remains empty, as this build mode does not produce a user-space binary.System Call Stubs: It includes
staging/libstubs.ainNUTTXLIBS, providing the kernel-side implementation for system calls. User-space system call proxies (libproxies.a) are not included, consistent with the absence of a user-space build.Conditional Library Inclusion: It uses
ifeq ($(CONFIG_XXX),y)checks to conditionally include kernel-specific libraries for optional features based on the system’s Kconfig.Exporting User Libraries (Empty):
EXPORTLIBSis set to$(USERLIBS). Consequently, in this kernel-only build mode,EXPORTLIBSwill be empty, reflecting that no user-space libraries are produced for external packaging.
Directories.mk
This file defines the directories used throughout the NuttX build process. These directory lists are not static but are dynamically constructed based on the active Kconfig configuration.
The file begins by defining a BASEDIRS variable, which includes core directories
that are always part of the build. Subsequently, it uses conditional logic
(ifeq ($(CONFIG_XXX),y)) to append additional directories to various master lists,
such as ALLDIRS and CLEANDIRS, only if their corresponding Kconfig options are
enabled.
For example:
The
net/directory is added to the build lists only ifCONFIG_NET=y.The
graphics/directory is included ifCONFIG_GRAPHICS=y.The
crypto/directory is included ifCONFIG_CRYPTO=y.
The file defines key variables that hold these dynamically generated directory lists:
KERNDEPDIRS: Directories containing kernel files for dependency generation.USERDEPDIRS: Directories containing user-space files for dependency generation.CCLEANDIRS: Directories where theclean_contexttarget will execute.CLEANDIRS: All known directories where thecleantarget will execute.CONTEXTDIRS: Directories with special pre-build requirements, such as auto-generation of files or symbolic link creation.
LibTargets.mk
The LibTargets.mk file defines all the targets needed to build the NuttX
libraries from their respective source directories and then install the
resulting library file into the staging/ directory.
For many core libraries (e.g., libc, libm, libmm, libbuiltin, libnx, libarch),
LibTargets.mk defines distinct targets for kernel and user variants.
Kernel libraries are typically prefixed with
libk(e.g.,libkc.afor kernel C library,libkmm.afor kernel memory management). These are compiled with specificEXTRAFLAGSthat include$(KDEFINE), which sets kernel-specific preprocessor definitions (e.g.,-D__KERNEL__). This ensures they are built with the necessary context and APIs for the kernel environment.User libraries retain their standard names (e.g.,
libc.a,libmm.a). These are compiled without the$(KDEFINE)flag, making them suitable for user-space applications. The build process for user-mode libraries often depends onpass1dep, while kernel-mode libraries depend onpass2dep, reflecting the two-pass build strategy in protected and kernel build modes.
For each variant of a library, two main targets are defined:
A build target: This target recursively calls
makewithin the library’s source directory (e.g.,libs/libc/,mm/) to compile the sources and create the library archive (.a) file. It passes the appropriateEXTRAFLAGSdepending on whether it’s a kernel-mode or user-mode build. The dependency for these targets is eitherpass1deporpass2dep, ensuring dependencies are checked before building.A staging target: This target depends on the successful creation of the library archive from the previous step. Its recipe calls the
INSTALL_LIBmacro (defined inUnix.mkorWin.mk) to copy the newly created library to the top-levelstaging/directory.
The file contains conditional logic, primarily based on CONFIG_BUILD_FLAT,
to adjust dependencies (pass1dep vs. pass2dep) for libraries that can
be part of either the user or kernel space, ensuring they are built in the
correct pass according to the selected build mode.