CMake Organization and Usage Guide#
- Author:
Jakub Janeczko
- Date:
24-02-2026
Overview#
BigOS uses CMake (version 3.24+) as its primary build system to compile a RISC-V 64-bit operating system kernel and supporting examples. The build system is organized to support multiple compiler toolchains (GCC and Clang), various build configurations, and automated binary generation and testing.
Directory Structure#
BigOS/ ├── CMakeLists.txt # Root CMake configuration ├── CMakePresets.json # Build presets for easy configuration ├── CMake/ # CMake modules and configuration │ ├── CommonConfig.cmake # Shared build functions and configuration │ ├── Linters.cmake # Linter integration (clang-tidy, include-what-you-use) │ ├── preset-mixins.json # Preset configuration mixins │ └── toolchains/ # Cross-compilation toolchain definitions │ ├── riscv64-common.cmake # Common RISC-V settings │ ├── riscv64-gcc.cmake # GCC-specific toolchain │ └── riscv64-clang.cmake # Clang-specific toolchain ├── src/ # Source code for all components │ ├── CMakeLists.txt │ ├── kernel/ # OS kernel │ └── lib/ # Libraries └── build/ # Build output directory
Build Configurations#
CMakePresets.json#
Provides pre-configured build profiles for easy project setup:
Some notable examples: * debug: GCC with debug symbols * release: GCC with optimization
debug-clang: Clang with debug symbols and code analysis
release-clang: Clang with optimization
ninja-multi: Multi-configuration Ninja build with GCC (see below)
ninja-multi-clang: Multi-configuration Ninja build with Clang
release-lto: GCC with optimization and link-time optimization
release-lto-clang: Clang with optimization and link-time optimization
ninja-multi-lto: Multi-configuration Ninja build with GCC and link-time optimization
ninja-multi-lto-clang: Multi-configuration Ninja build with Clang and link-time optimization
Using Presets#
Configure with a preset:
cmake --preset debug
Then build:
cmake --build --preset debug
Or configure manually:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=CMake/toolchains/riscv64-gcc.cmake \ -DCMAKE_BUILD_TYPE=Debug cmake --build buildNote
If you pull in a new patch that possibly changes: - CMakePresets.json - CMake/preset-mixins.json - any file in CMake/toolchains
- You NEED to cleanup the build directory:
rm -rf build cmake --preset "<my-preset>"
or
cmake --fresh --preset "<my-preset>"
Please check if this before submitting any issues.
Build Targets#
To build every binary just run:
cmake --build build # -j for parallel builds
The output files should be in specific subdirectory of ./build following the project directory structure.
All libraries and executables have their own targets for building. Additionally some binaries have an additional run-* and debug targets.
Those can be run with the following commands:
cmake --build build -t run-<target-name> cmake --build build -t debug-<target-name>
Tab-completion should help you find the target names.
Generators#
You can additionally specify a generator. By default, CMake uses Unix Makefiles on linux. You can also use Ninja for faster builds.
cmake --preset debug --generator Ninja cmake --build --preset debug --config Debug
or you can use ninja-multi generator to have all available build types at once:
cmake --preset ninja-multi cmake --build build --config Debug cmake --build build --config Release cmake --build build --config RelWithDebInfo
Please take note that parallel builds are supported with all generators, but Ninja has it by default. For other generators use:
cmake --build build -j
Linters#
Integrates static analysis tools into the build:
BIGOS_USE_CLANG_TIDY: Enable clang-tidy code analysis (default: OFF)
BIGOS_USE_INCLUDE_WHAT_YOU_USE: Check for unnecessary includes (default: OFF)
When enabled, these tools run automatically during compilation.
You can enable those by providing them as CMake options:
cmake -DBIGOS_USE_CLANG_TIDY=ON -DBIGOS_USE_INCLUDE_WHAT_YOU_USE=ON --preset="<preset-name>"
QEMU Settings#
To modify QEMU settings you can use the following command:
cmake -B build \ -DBIGOS_QEMU_OPTIONS="-machine virt -serial mon:stdio -nographic -m 256M"
Toolchain Files#
Cross-compilation for RISC-V 64-bit targets requires specialized toolchain configuration.
riscv64-common.cmake * Sets cross-compilation target to RISC-V 64 * Defines RISC-V architecture: rv64ima_zicsr_zifencei * Defines ABI: lp64 * Defines code model: medany * Sets common compiler flags: -ffreestanding -nostdlib
riscv64-gcc.cmake * Includes common RISC-V configuration * Auto-detects GCC toolchain prefix * Supports multiple prefix formats: riscv64-unknown-linux-elf, riscv64-linux-gnu, etc.
riscv64-clang.cmake * Similar to GCC variant but for Clang compiler
Other options#
To list all available options see:
cmake -LH build
To set a variable just run:
cmake -D<variable>=<value> build
Please take note that all ABI-breaking changes should be made on a clean build directory and not as a modification:
cmake --preset="<preset_name>" -DVAR1=value1 -DVAR2=value2 --fresh
If you don’t know if an option is ABI-breaking, err on the side of caution.
CMake Components#
The project supplies some helper functions and macros:
SETUP_LIBRARY(name) * Initializes a library target * Automatically collects all .c and .S/.s assembly files * Collects headers from include/<name>/ directory * Applies common configuration
SETUP_EXECUTABLE(name) * Initializes an executable target * Automatically collects all .c, .h, and .S/.s files * Applies common configuration
COMPILE_BINARY(name) * Converts executable to raw binary format (.bin) * Uses objcopy to strip ELF headers * Useful for bootloaders and kernels
ADD_QEMU_TARGET(name) * Creates run targets for QEMU emulation * Creates run-<name> target to execute the binary * Creates debug-<name> target with GDB debugging support (-S -s) * Optional BIOS_IMAGE parameter to use as BIOS instead of kernel
Library Example: newlib#
in a subdirectory of src/lib/ create ‘newlib’
add CMakeLists.txt to the directory
optionally add include/newlib/ directory for headers
CMakeLists.txt
SETUP_LIBRARY(newlib)
This automatically setups everything you need.
The sources are in the same directory as CMakeLists.txt. The public headers are in include/newlib/ and private are with the sources.
After that you can add any customizations needed, e.g: * dependencies:
target_link_libraries(newlib PUBLIC lib1 lib2 ... # for libraries that should be traisitively added PRIVATE lib3 lib4 ... # for libraries for internal use (not visible in public headers) )
Please make sure that when linking multiple libraries, you are not defining a circular dependency. When defining dependencies you also need to define them in natural order: * if b depends on a, b must be before a in any target_link_libraries calls
Take note that if you define:
target_link_libraries(b PUBLIC a) # correct # `c-good` links to `b` that links to `a` # -> only one instanciation of the library target_link_libraries(c-good PUBLIC b a) # bad target_link_libraries(c-bad PUBLIC a b) # here we give additional constraint for # `c-bad` to link to `a` first and `b` second, # so the linking will have double instanciation of `a` # -> this will break the LTO compilation on clang
Executable Example: newbin#
in a subdirectory of src/ create ‘newbin’
add CMakeLists.txt to the directory
SETUP_EXECUTABLE(newbin) target_link_libraries(newbin PRIVATE newlib) # same as above COMPILE_BINARY(example_sbi) # creates a raw binary for consumption by qemu ADD_QEMU_TARGET(example_sbi) # add run-newbin and debug-newbin targets
Output Files#
Built binaries are located in:
build/src/<component>/ ├── <name> # ELF executable └── <name>.bin # Raw binary (bootable)
Project Layout Best Practices#
Libraries: Put reusable code in src/lib/ with headers in include/
Executables: Put in src/
Headers: Public headers go in include/<component>/, private in source directory