- Introduction
- Building Tilck's toolchain
- Building Tilck
- Configuring Tilck
- Build types
- Running the UEFI bootloader on QEMU
- Building Tilck's unit tests
- Building Tilck with Clang (advanced)
- Special build configurations (advanced)
Tilck has a CMake-based build system relying on a local toolchain, built by the
build_toolchain Bash script. The first time Tilck's repo is cloned, it is
necessary to build Tilck's local toolchain by running that script. After that,
it will be possible to prepare Tilck's build by running CMake in any chosen
build directory. While running cmake directly it is possible, the strongly
recommended practice in Tilck is to run the wrapper script cmake_run instead.
That script checks the version of the GCC compiler on the machine, offers handy
shortcuts, allows us to use a CMake binary from the local toolchain, and other
things.
When run without any options, the build_toolchain script installs a minimal
set of packages (e.g. cross GCC toolchain, busybox, etc.) in the local toolchain.
While it's part of Tilck's philosophy to have as fewer dependencies as possible,
there still are some packages required to be installed at system level
(e.g. gcc, git, wget, tar, grep, make, etc.) simply because they're needed to
build Tilck's toolchain itself. Once run, the build_toolchain script will take
care of detecting which packages need to be installed and it will also run the
right command for your Linux distribution to install them (e.g.
sudo apt install gcc g++ git [...]). Most of the mainstream Linux distributions
are supported. In the case you're using a distro not supported, the script will
just dump a list of programs that need to be installed manually on the system
before the script could continue further.
After the first run of build_toolchain finishes, it's possible to build Tilck
but, that doesn't mean the script becomes useless. Actually, most of the
packages that it can install are not installed by default. The idea behind that
is to perform the first setup as quickly as possible. To see all the available
packages and their install status, run:
./scripts/build_toolchain -l
Single packages can be installed using the -s option. For example:
./scripts/build_toolchain -s vim tcc lua
Packages that support interactive reconfiguration (e.g. busybox, u-boot) can
be reconfigured with -C:
./scripts/build_toolchain -C busybox
For the full list of options, run ./scripts/build_toolchain -h. For detailed
documentation about the package manager, see package_manager.
To build Tilck, it's necessary to run first <TILCK_DIR>/scripts/cmake_run in
the chosen build directory (e.g. ~john/builds/tilck01) and then just run
make there. That means that out-of-tree builds are supported
effortlessly.
But, because 99% of the time it's just fine to have a build directory inside
our project's main directory (e.g. ~john/devel/tilck), we can simple run
./scripts/cmake_run there. It will detect that's our main directory and it
will create the build subdir. After that, we'll still have to enter the
build directory and run make there, because that's where CMake placed its
generated makefiles. In reality, there's a more convenient shortcut: just
run make in project's main directory, using the trivial wrapper Makefile
included in Tilck's source: it will simply run $(MAKE) -C build. In case the
build/ directory does not exist yet, it will also run the cmake_run wrapper
script first.
To speed up Tilck's build, it's possible to increase the level of parallelism
with GNU make's option -j<N>. For example, make -j4 means that at most
4 instances of the compiler will be run in parallel. WARNING: increasing
the level of parallelism requires significantly more memory in the host system
It's hard to estimate in general how much memory each instance of GCC or Clang
will use for compiling a C or C++ file, but it makes sense to be conservative
and to account for ~1 GB of RAM per GCC/Clang instance. Therefore, make -j4
would require at least 4 GB of available memory in the host system used for
building Tilck. Still, it's possible to increase the level of parallelism at
your own discretion, keeping in mind that in case the Linux host system runs
out of memory while building this project (or anything else!), the OOM killer
will run and that will make the system potentially unstable/unusable.
See:
- Issue #101
- https://unix.stackexchange.com/questions/316644/
- https://unix.stackexchange.com/questions/208568/
- https://unix.stackexchange.com/questions/153585/
- https://lwn.net/Articles/317814/
When cmake_run is run, all configuration options are dumped on the screen as
CMake info messages. Therefore, changing one of them is easy as running:
./scripts/cmake_run -DDEBUG_CHECKS=0
(Note: cmake_run forwards its arguments to CMake.) But, that's certainly not
the best way to (re)configure Tilck. A more convenient way is to use CMake's
console tool called ccmake, part of cmake-curses-gui package (at least on
Debian systems). Its main advantage is that all the options are visible and
editable in an interactive way and that for each option there is a description.
It's not visually cool and fancy like Linux's Kconfig, but it's fully integrated
with CMake and does a good job, overall. (Maybe in the future Tilck will switch
to Kconfig or some custom tool for a better user experience even in this case.)
Running ccmake is simple as:
ccmake ./build
But, as for the cmake_run case, it's highly recommended to run the
run_config helper instead. Since it needs to know the exact build
directory, its paths, and where the host_menuconfig toolchain package is
installed, CMake generates it automatically at configure time from the
template at scripts/templates/run_config.in. The generated script lives
next to the build, so for in-tree builds it is simply:
./build/run_config
For out-of-tree builds, the file is created under the build directory you
used (<build_dir>/run_config). It launches mconf against the Tilck
Kconfig generated from the tilck_option() metadata; pass --ccmake
to fall back to the classic ccmake UI.
Finally, there is also a wrapper for people used with Kconfig too, working when the in-tree build directory is used (again, 99% of the time):
make config
Or:
make menuconfig
They have both the same effect as running run_config. In summary, it's
convenient to use run_config or make config instead of running ccmake
directly because of its error-checking and because at some point Tilck will
have something with a better UI than ccmake that will be run by the wrapper
scripts.
Tilck's build types differ only by the optimization flags used. Three build types are currently supported:
- Debug:
-O0 -fno-inline-functions - Release:
-O3 - MinSizeRel:
-Os
Tilck's default build type is: Debug. It's worth noting that very few projects
are built by default with -O0 -fno-inline-functions because the code produced
is very inefficient. Fortunately, Tilck still performs very well in this case.
Therefore, it's convenient to use this build type in particular when debugging
Tilck because the value of all local variables will be visible in GDB
(no optimizations, no inlining).
Changing Tilck's build type is simple as running:
./scripts/cmake_run -DCMAKE_BUILD_TYPE=Release
For Release builds there's a nice shortcut:
REL=1 ./scripts/cmake_run
Finally, it's necessary to remark that, because level of compiler's optimization
is the only thing that distinguishes the build types, ASSERTs are checked no
matter the build type. To compile them out, it's necessary to turn off the
DEBUG_CHECKS flag.
Tilck's image is bootable on a pure UEFI machine. That can be tested using:
./build/run_efi_qemu64
Notes:
-
This script runs a x86_64 machine with a 64-bit UEFI bootloader, that is close to what we can try on real hardware today. The UEFI bootloader switches from
long modetoprotected mode 32before jumping to the 32-bit Tilck kernel. -
Using UEFI on QEMU requires an EDKII firmware, that might or might not be installed as part of QEMU. On Linux, when installed, OVMF can be found at: /usr/share/OVMF/. On FreeBSD, when installed, EDKII builds can found at: /usr/local/share/qemu/edk2-*. Those need to be installed separately from QEMU itself.
-
There's a
run_efi_qemu32script as well and can be used if the user provides a OVMF build for 32-bit x86.
Tilck uses the googletest framework for unit tests and that's not downloaded
by build_toolchain when it's run without arguments. In order to install that
framework in the toolchain, just run:
./scripts/build_toolchain -s host_gtest
After that, you'll need to run the cmake_run script again:
./scripts/cmake_run # For in-tree builds
Or:
./scripts/cmake_run <BUILD_DIR> # For out-of-tree builds
Finally, just build the unit tests with:
make gtests
To run them, execute:
./build/gtests
Note: there's more about the unit tests, including special build configurations. For all of that, see the testing document.
This use case is not supported anymore and setting USE_SYSCC=1 now triggers an error. Thanks to the custom pre-built toolchains from musl-cross-make, which are statically linked, include libmusl debug info and support all the combinations of {host arch, target arch, gcc version} we need, we could finally remove the support for building the whole project with a system GCC compiler and force it to use our libmusl instead of glibc. That is a massive simplification for this project.
The Tilck project cannot be entirely built with a Clang toolchain, at the moment. But, it still has partial support for Clang because:
-
Supporting more than compiler (in general) improves the code
-
Clang sometimes reports warnings in cases where GCC doesn't (the opposite is true as well)
-
The
-Wconversionflag as implemented in Clang is very useful -
Clang's Static Analyzer is great
-
Tilck has a custom plug-in for the Clang's Static Analyzer (but currently it's not open source)
- Install
clangandclang++and make sure it can build i686 binaries. - Run:
CC=clang CXX=clang++ ./scripts/cmake_run -DKERNEL_SYSCC=1 - Build as always with
make
The KERNEL_SYSCC option makes the build system to use CC and CXX to build
kernel's C and C++ files, while still building the assembly files with the
pre-built GCC cross-compiler from musl-cross-make. Note: no matter the
KERNEL_SYSCC option, kernel's unit tests (see testing) are always built
using system's compiler. Therefore, setting CC=clang CXX=clang++ will cause
the unit tests to be completely built with Clang: this scenario is fully
supported and has no tricky limitations.
-
Major problem:
llvm-asis not 100% compatible withgas. In particular, it does not support expressions as literals (e.g. 0x1000 + 32 * 8) in the same waygasdoes. -
Minor problem: Clang does not support the Microsoft ABI when the output's type is not a PE binary, while GCC does not support PE as output at all. That prevents using Clang to build the EFI bootloader. A dedicated build of the EFI bootloader for Clang can be written, but it would be very different from the GCC one but, supporting two completely different builds for the same target is an overkill, especially because it wouldn't be enough for building everything with Clang (see the problem above).
Tilck has a predefined list of supported configurations which must always build and pass all the tests. Most of them are built & run each time a branch is pushed by the Azure Pipelines CI but some of them are left out because of the limited resources available there. Anyway, independently of the amount of resources available in CI builds, we need a simple way to test everything on our local machines.
The directory scripts/build_generators contains shell scripts, each creating a
different build configuration by calling cmake_run with a specific set of
options. The recommended way to run all of them is through the package manager's
test infrastructure:
# Install all packages + build all archs + all build generator configs
./scripts/build_toolchain -t --system-tests --all-build-types
# Preview what will run (no actual execution)
./scripts/build_toolchain -t -d --system-tests --all-build-types -a ALL
# Also run Tilck's unit tests and system tests after each build
./scripts/build_toolchain -t --system-tests --all-build-types --run-also-tilck-tests
For more details, see package_manager.
There is also a standalone ./scripts/adv/gen_other_builds script which builds
the kernel and its unit tests for each configuration without going through the
package manager's test runner. Note: these scripts expect additional packages
to be installed (clang, gtest, lcov). Before running them for the first time,
check that the project builds with both GCC and Clang.