Skip to content

Latest commit

 

History

History
326 lines (238 loc) · 14.1 KB

File metadata and controls

326 lines (238 loc) · 14.1 KB

Building & configuring Tilck

Contents

Introduction

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.

Building Tilck's toolchain

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.

Extra packages

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.

Building Tilck

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.

Build performance notes

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:

Configuring Tilck

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.

Build types

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.

Running the UEFI bootloader on QEMU

Tilck's image is bootable on a pure UEFI machine. That can be tested using:

./build/run_efi_qemu64

Notes:

  1. 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 mode to protected mode 32 before jumping to the 32-bit Tilck kernel.

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

  3. There's a run_efi_qemu32 script as well and can be used if the user provides a OVMF build for 32-bit x86.

Building Tilck's unit tests

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.

Building Tilck with system's compiler (advanced)

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.

Building Tilck with Clang (advanced)

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 -Wconversion flag 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)

How to build with Clang

  1. Install clang and clang++ and make sure it can build i686 binaries.
  2. Run: CC=clang CXX=clang++ ./scripts/cmake_run -DKERNEL_SYSCC=1
  3. 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.

Why we can't build the whole project with Clang?

  1. Major problem: llvm-as is not 100% compatible with gas. In particular, it does not support expressions as literals (e.g. 0x1000 + 32 * 8) in the same way gas does.

  2. 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).

Special build configurations (advanced)

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.