Build System Internals & Developer Guide
This document is the definitive guide to the TilekarOS build system. It is designed for developers who need to understand, extend, or debug the compilation process.
1. Philosophy & Architecture
TilekarOS uses CMake (v3.15+) as its primary build system generator. While Makefiles exist in the project, they are thin wrappers (Facades) intended for user convenience (make, make iso), while CMake handles the heavy lifting of dependency graph generation, compiler configuration, and cross-compilation toolchains.
Why CMake?
- Cross-Platform: Generates Makefiles, Ninja build files, or VS Code / IDE projects seamlessly.
- Multi-Architecture: Easy switching between
i386,x86_64,arm, etc., using toolchain files. - Dependency Management: Correctly handles complex chains like C Header \(\rightarrow\) Python Script \(\rightarrow\) ASM Include \(\rightarrow\) ASM Object \(\rightarrow\) Kernel Binary.
The Build Pipeline
The build process transforms raw C and Assembly source code into a bootable ISO image.
graph TD
subgraph Phase1 ["Phase 1: Configuration"]
CMake[CMake] -->|Reads| Toolchain["cmake/toolchains/ARCH.cmake"]
CMake -->|Reads| CM_Root["CMakeLists.txt"]
CMake -->|Generates| BuildEnv["Build Directory (Makefiles)"]
end
subgraph Phase2 ["Phase 2: Pre-Processing"]
Config_H[include/kernel/config.h]
Helper[helpers/h2inc.py]
Helper -->|Parses| Config_H
Helper -->|Generates| Config_Inc[build/config.inc]
end
subgraph Phase3 ["Phase 3: Compilation"]
direction LR
Config_Inc -.->|Included| ASM_Src["Assembly Sources"]
ASM_Src -->|nasm| ASM_Obj["ASM Objects"]
C_Src["C Sources"] -->|clang| C_Obj["C Objects"]
LibC_Src["LibC Sources"] -->|clang| LibC_Lib["liblibc.a"]
end
subgraph Phase4 ["Phase 4: Linking"]
ASM_Obj & C_Obj & LibC_Lib --> Linker[ld.lld]
Linker -->|arch/ARCH/linker.ld| Kernel["myos.kernel"]
end
subgraph Phase5 ["Phase 5: Packaging"]
Kernel -->|cp| ISO_Dir[isodir]
GRUB[grub.cfg] -->|cp| ISO_Dir
ISO_Dir -->|grub-mkrescue| ISO["myos.iso"]
end
2. Directory Structure & Organization
The build system relies on a strict directory hierarchy to locate architecture-specific code.
/
├── CMakeLists.txt # Root configuration
├── Makefile # Wrapper for CMake
├── cmake/
│ └── toolchains/
│ ├── i386.cmake # Toolchain for i386-elf
│ └── x86_64.cmake # (Future) Toolchain for x86_64-elf
├── kernel/
│ ├── CMakeLists.txt # Kernel target definition
│ └── arch/
│ ├── i386/ # i386 specific C/ASM code & linker scripts
│ └── x86_64/ # (Future) x86_64 specific code
├── libc/
│ ├── CMakeLists.txt # LibC target definition
│ └── arch/
│ └── i386/ # Architecture-optimized LibC functions
└── sysroot/ # Virtual filesystem root for cross-compilation
3. Multi-Architecture Support
The build system is designed to be Architecture Agnostic. The target architecture is controlled by the OS_ARCH variable.
How it works
- Selection: The user selects an architecture (default:
i386). - Toolchain Loading: CMake looks for
cmake/toolchains/${OS_ARCH}.cmake. This file sets the compiler (clang), assembler (nasm), and target flags (--target=...). - Source Selection:
- Kernel:
kernel/CMakeLists.txtglobs files fromkernel/arch/${OS_ARCH}/*. - LibC:
libc/CMakeLists.txtglobs files fromlibc/arch/${OS_ARCH}/*.
- Kernel:
- Emulation: The
runtargets dynamically choose the emulator (e.g.,qemu-system-${OS_ARCH}).
Adding a New Architecture
To port TilekarOS to a new architecture (e.g., riscv64):
- Create Toolchain: Add
cmake/toolchains/riscv64.cmake. - Create Kernel Arch Directory: Create
kernel/arch/riscv64/.- Add
boot.asm/entry.S. - Add
linker.ld(Linker script). - Add
local_config.h.
- Add
- Create LibC Arch Directory: Create
libc/arch/riscv64/.- Add architecture-specific optimizations (or leave empty if generic C is fine).
- Build: Run
make ARCH=riscv64.
4. Helper Scripts & Custom Commands
helpers/h2inc.py (The Bridge)
Problem: We define constants like GDT_OFFSET_KERNEL_CODE = 0x08 in C header files (config.h). The assembly code (boot.asm) needs these exact values to set up segments. Hardcoding them in two places leads to "Magic Number" bugs.
Solution:
- CMake invokes
h2inc.pyduring the build. - The script reads C
#definemacros. - It outputs a NASM-compatible
%definefile (build/config.inc). - NASM files include this generated file via the
-Pflag.
Usage in CMake:
add_custom_command(
OUTPUT ${CONFIG_INC}
COMMAND Python3::Interpreter "${CMAKE_SOURCE_DIR}/helpers/h2inc.py" ${CONFIG_H} ${CONFIG_INC}
...
)
5. IDE Integration (.clangd)
To ensure a smooth development experience in editors like VS Code or Zed (which use clangd), the root CMakeLists.txt generates a .clangd file.
This file tells the language server:
* We are cross-compiling (e.g., --target=i386-elf).
* Where to find the kernel headers (-Ikernel/include).
* Where to find the system headers (-Isysroot/usr/include).
Result: You get accurate Autocomplete and Error Checking for the target OS, not the host Linux system.
6. Command Reference
| Command | Variables | Description |
|---|---|---|
make |
ARCH=... |
Builds the kernel binary. |
make iso |
ARCH=... |
Builds the bootable ISO image. |
make run |
ARCH=... |
Runs the kernel binary directly in QEMU. |
make run_iso |
ARCH=... |
Boots the ISO in QEMU (Tests GRUB integration). |
make clean |
Removes the build/ directory. |
|
make configure |
ARCH=... |
Re-runs the CMake configuration step manually. |
Example: