Build System and Toolchains
About 1239 wordsAbout 4 min
2026-03-23
BaseTools
BaseTools/ is the collection of Python scripts and compiled C utilities that implement the EDK2 build pipeline. It is not a traditional Makefile-based system — the build command is a Python orchestrator that generates and invokes Makefiles or NMake files per-module.
BaseTools/
├── BinWrappers/ ← Shell wrappers that launch Python or C tools
├── Bin/ ← Pre-compiled C tool binaries (Windows)
├── Source/C/ ← C source for binary tools
│ ├── GenFv/ ← Builds Firmware Volumes
│ ├── GenFfs/ ← Wraps PE32 binaries into FFS files
│ ├── GenFw/ ← PE/COFF manipulation (stripping, TE conversion)
│ ├── GenSec/ ← Wraps content into Sections
│ ├── VfrCompile/ ← Compiles .vfr (Visual Forms Representation) to IFR
│ ├── VolInfo/ ← Inspects FV/FFS images
│ └── LzmaCompress/ ← LZMA compression for FV sections
└── Source/Python/ ← Python tools
├── build/ ← Main build orchestration (build.py called by 'build' command)
├── AutoGen/ ← Auto-generates C headers from INF/DEC/DSC metadata
├── Common/ ← Shared parsing libraries
├── Ecc/ ← Coding-style/error checker
├── GenPatchPcdTable/ ← Generates PCD patch table for PatchableInModule
├── PatchPcdValue/ ← Applies post-build PCD patches to PE32 binaries
└── Trim/ ← Preprocesses ASL/VFR sourceBuilding BaseTools from Source
cd edk2/
source edksetup.sh BaseTools # sets env vars AND builds BaseTools C tools
# or explicitly:
make -C BaseTools/Source/C/On clean checkouts, the C tools must be compiled. edksetup.sh BaseTools handles this automatically on Linux/macOS. On Windows use edksetup.bat Rebuild.
Build Environment Setup
edksetup.sh / edksetup.bat
edksetup.sh is the environment initialization script. It must be sourced (not executed) in every new shell session before using the build command.
# Minimum: set WORKSPACE and EDK_TOOLS_PATH, add build to PATH
source edk2/edksetup.sh
# With specific external packages outside edk2/
export PACKAGES_PATH=/path/to/edk2:/path/to/edk2-platforms:/path/to/edk2-non-osi
source edk2/edksetup.shEnvironment variables set by edksetup.sh:
| Variable | Purpose | Example Value |
|---|---|---|
WORKSPACE | Root for all relative paths in DSC/FDF | /build/edk2 |
EDK_TOOLS_PATH | Location of BaseTools | ${WORKSPACE}/BaseTools |
CONF_PATH | Location of target.txt, tools_def.txt | ${WORKSPACE}/Conf |
PACKAGES_PATH | Colon-separated search path for packages | /src/edk2:/src/edk2-platforms |
PYTHON_COMMAND | Python interpreter to use | python3 |
Conf/target.txt
This file configures the default build target. The build command reads it unless command-line flags override.
ACTIVE_PLATFORM = OvmfPkg/OvmfPkgX64.dsc
TARGET = DEBUG
TARGET_ARCH = X64
TOOL_CHAIN_CONF = Conf/tools_def.txt
TOOL_CHAIN_TAG = GCC5
BUILD_RULE_CONF = Conf/build_rule.txt
MAX_CONCURRENT_THREAD_NUMBER = 8ACTIVE_PLATFORM— the DSC file to build (relative toPACKAGES_PATH)TARGET—DEBUG,RELEASE, orNOOPTTARGET_ARCH— one or more space-separated architectures:IA32,X64,AARCH64,ARM,RISCV64,LOONGARCH64TOOL_CHAIN_TAG— selects a row intools_def.txt
Toolchain Tags
A toolchain tag is a string key that maps to a set of compiler/linker/assembler binaries in tools_def.txt. This indirection allows the same DSC/INF to be built with GCC, Clang, or MSVC without source modification.
Common Toolchain Tags
| Tag | Compiler | Architecture Support | Notes |
|---|---|---|---|
GCC5 | GCC 5.x+ | IA32, X64, AARCH64, ARM | Primary Linux toolchain |
GCC_ARM | GCC (ARM-specific) | ARM32 (legacy) | For bare-metal ARM32 when GCC5 fails |
CLANG38 | CLANG 3.8+ | X64, AARCH64 | Clang with GNU binutils |
CLANGDWARF | CLANG (DWARF debug) | X64, AARCH64 | Clang with DWARF for source debug |
CLANGPDB | CLANG (PDB debug) | X64, AARCH64 | Clang with PDB symbols (Windows-like) |
VS2019 | MSVC 2019 | IA32, X64 | Native Windows builds |
XCODE5 | Xcode/Apple Clang | X64, AARCH64 | macOS host |
GCC_AARCH64 | aarch64-linux-gnu-gcc | AARCH64 | Cross-compile from x86-64 host |
tools_def.txt Structure
# DEFINE: Macros evaluated within tools_def.txt
DEFINE GCC5_AARCH64_PREFIX = aarch64-linux-gnu-
# Format: TAG_VERSION_ARCH_TOOLCODE_ATTRIBUTE = value
# TOOLCODE values: CC, CXX, AS, AR, LD, DLINK, OBJCOPY, ...
*_GCC5_AARCH64_CC_PATH = DEF(GCC5_AARCH64_PREFIX)gcc
*_GCC5_AARCH64_CXX_PATH = DEF(GCC5_AARCH64_PREFIX)g++
*_GCC5_AARCH64_AS_PATH = DEF(GCC5_AARCH64_PREFIX)gcc
*_GCC5_AARCH64_AR_PATH = DEF(GCC5_AARCH64_PREFIX)ar
*_GCC5_AARCH64_LD_PATH = DEF(GCC5_AARCH64_PREFIX)ld
*_GCC5_AARCH64_OBJCOPY_PATH = DEF(GCC5_AARCH64_PREFIX)objcopy
*_GCC5_AARCH64_OBJDUMP_PATH = DEF(GCC5_AARCH64_PREFIX)objdump
*_GCC5_AARCH64_DTCPP_PATH = DEF(GCC5_AARCH64_PREFIX)cpp
*_GCC5_AARCH64_CC_FLAGS = DEF(GCC5_ALL_CC_FLAGS) -mcmodel=small -fno-stack-protector
*_GCC5_AARCH64_DLINK_FLAGS = DEF(GCC5_AARCH64_DLINK_FLAGS)
*_GCC5_AARCH64_ASLDLINK_FLAGS = DEF(GCC5_AARCH64_ASLDLINK_FLAGS)The wildcard * in position 1 (version) means "any version tag". You can narrow it:
DEBUG_GCC5_AARCH64_CC_FLAGS = ... -g -O0
RELEASE_GCC5_AARCH64_CC_FLAGS = ... -O2The build Command
build [options]Key Options
| Option | Long Form | Description |
|---|---|---|
-p FILE.dsc | --platform | DSC file to build |
-b TARGET | --buildtarget | DEBUG, RELEASE, NOOPT |
-a ARCH | --arch | Architecture(s): X64, AARCH64, etc. |
-t TAG | --tagname | Toolchain tag |
-n N | --thread-number | Parallel jobs |
-m FILE.inf | --module | Build a single module only |
-s | --silent | Suppress tool output |
-q | --quiet | Minimal output |
-D KEY=VALUE | --define | Override a DSC macro |
cleanall | — | Delete entire Build/ output directory |
clean | — | Delete object files but keep images |
fds | — | Rebuild FD images only (no recompile) |
Build Command Examples
# Build OVMF for X64 in DEBUG mode
build -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG -a X64 -t GCC5
# Cross-compile ARM virt platform
build -p ArmVirtPkg/ArmVirtQemu.dsc -b RELEASE -a AARCH64 -t GCC5
# Multi-arch build (IA32 + X64)
build -p OvmfPkg/OvmfPkgIa32X64.dsc -a IA32 -a X64 -t GCC5 -b DEBUG
# Build a single module for quick iteration
build -p OvmfPkg/OvmfPkgX64.dsc -m MdeModulePkg/Core/Dxe/DxeMain.inf -a X64 -t GCC5
# Override a DSC macro at build time
build -p MyPlatform/MyPlatform.dsc -D ENABLE_TPM=TRUE -D SECURE_BOOT_ENABLE=TRUE
# Clean and rebuild
build cleanall
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5 -b RELEASEBuild Output Directory Structure
Build/
└── OvmfX64/ ← OUTPUT_DIRECTORY from DSC [Defines]
└── DEBUG_GCC5/ ← {TARGET}_{TOOL_CHAIN_TAG}
└── X64/ ← arch
├── MdeModulePkg/
│ └── Core/
│ └── Dxe/
│ └── DxeMain/
│ ├── DEBUG/ ← intermediate .o files
│ ├── OUTPUT/
│ │ └── DxeMain.efi ← PE32 output
│ └── GNUmakefile ← generated per-module Makefile
├── AutoGen/
│ └── MdeModulePkg/Core/Dxe/DxeMain/
│ ├── AutoGen.h ← generated PCD getter macros
│ └── AutoGen.c ← generated PCD library glue
└── FV/
├── OVMF.fd ← Final firmware device image
├── FVMAIN_COMPACT.Fv
└── FVMAIN.FvAutoGen Files
The build system generates AutoGen.h and AutoGen.c for every module. These implement:
_PCD_VALUE_*macros for FixedAtBuild PCDsPcdGet*()/PcdSet*S()stubs for Dynamic PCDs_gPcd_BinaryPatch_*extern declarations for PatchableInModule PCDs- Unicode string tables for HII modules
This is why removing AutoGen from .gitignore is wrong — it is fully generated from metadata.
Cross-Compilation Setup (Linux Host → AARCH64 Target)
# Install GNU aarch64 toolchain
sudo apt-get install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
# Verify
aarch64-linux-gnu-gcc --version
# Check tools_def.txt has GCC5 AARCH64 entries (usually present in upstream edk2)
grep "GCC5_AARCH64" Conf/tools_def.txt | head -5
# Build
export PACKAGES_PATH=/src/edk2:/src/edk2-platforms
source edk2/edksetup.sh
build -p edk2-platforms/Platform/RaspberryPi/RPi4/RPi4.dsc \
-a AARCH64 -t GCC5 -b DEBUGFor RISC-V cross-compilation:
sudo apt-get install gcc-riscv64-linux-gnu
build -p OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc -a RISCV64 -t GCC5 -b RELEASEStuart — TianoCore CI/Build Tool
Stuart (pip install edk2-pytool-extensions) is the higher-level build and CI tool maintained by Project Mu/TianoCore. It wraps the build command and adds:
- Dependency installation (submodule fetching, tool downloads)
- Pre/post build hooks
- Platform plug-in architecture
- CI workflows (code format checks, compile-test matrix)
pip install edk2-pytool-extensions edk2-pytool-library
# Stuart setup (fetches required submodules + tools)
stuart_setup -c .pytool/CISettings.py
# Stuart update (sync submodules)
stuart_update -c .pytool/CISettings.py
# Stuart build
stuart_build -c .pytool/CISettings.py -p OvmfPkg --arch X64 --target DEBUG
# Stuart CI (compile test all packages)
stuart_ci_build -c .pytool/CISettings.py -p OvmfPkgMost modern EDK2 platform repositories (edk2-platforms, Project Mu forks) use Stuart as the primary interface rather than calling build directly.
Debugging the Build System
Verbose Mode
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5 -b DEBUG --verbose 2>&1 | tee build.logModule Dependency Analysis
# Show which libraries are linked into a specific module
grep -r "LibraryClasses" Build/OvmfX64/DEBUG_GCC5/X64/MdeModulePkg/Core/Dxe/DxeMain/GNUmakefileCommon Build Errors and Causes
| Error | Likely Cause |
|---|---|
Error 7000: File not found | Missing PACKAGES_PATH entry or typo in INF [Packages] |
Error 2000: Invalid PCD | PCD referenced in INF not declared in any listed .dec |
ERROR - Library Instance ... is not found | DSC [LibraryClasses] missing a binding for this module type |
LINK : fatal error LNK1181 | MSVC specific: missing import lib; check [BuildOptions] DLINK flags |
undefined reference to 'CompilerIntrinsicLib' | Missing [LibraryClasses] binding for CompilerIntrinsicLib on this arch |
AutoGen.h: PcdXxx is not declared | Module uses a PCD whose token space GUID is not listed in [Packages] |