---
url: /kb/embedded/edk2/build-system-toolchains/index.md
---
# EDK2 Build System and Toolchains

## 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 source
```

### Building BaseTools from Source

```bash
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.

```bash
# 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.sh
```

Environment 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.

```ini
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 = 8
```

* `ACTIVE_PLATFORM` — the DSC file to build (relative to `PACKAGES_PATH`)
* `TARGET` — `DEBUG`, `RELEASE`, or `NOOPT`
* `TARGET_ARCH` — one or more space-separated architectures: `IA32`, `X64`, `AARCH64`, `ARM`, `RISCV64`, `LOONGARCH64`
* `TOOL_CHAIN_TAG` — selects a row in `tools_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

```ini
# 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:

```ini
DEBUG_GCC5_AARCH64_CC_FLAGS  = ... -g -O0
RELEASE_GCC5_AARCH64_CC_FLAGS = ... -O2
```

***

## The build Command

```bash
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

```bash
# 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 RELEASE
```

***

## Build 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.Fv
```

### AutoGen Files

The build system generates `AutoGen.h` and `AutoGen.c` for every module. These implement:

* `_PCD_VALUE_*` macros for FixedAtBuild PCDs
* `PcdGet*()` / `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)

```bash
# 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 DEBUG
```

For RISC-V cross-compilation:

```bash
sudo apt-get install gcc-riscv64-linux-gnu
build -p OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc -a RISCV64 -t GCC5 -b RELEASE
```

***

## Stuart — 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)

```bash
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 OvmfPkg
```

Most 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

```bash
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5 -b DEBUG --verbose 2>&1 | tee build.log
```

### Module Dependency Analysis

```bash
# Show which libraries are linked into a specific module
grep -r "LibraryClasses" Build/OvmfX64/DEBUG_GCC5/X64/MdeModulePkg/Core/Dxe/DxeMain/GNUmakefile
```

### Common 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]` |
