One of the most common pitfalls when contributing to Linux kernel development is that if you make subsystem-wide changes, it can become difficult to ensure everything still builds fine. Chances are that if the subsystem is moderately big you won't be able to build all drivers on a single architecture or configuration. Contributors may not always care about test-building all drivers, but at least maintainers will have to, otherwise chances are that they'll make life miserable for others.
Faking
The Linux kernel configuration system provides some assistance to maintainers through the
COMPILE_TEST Kconfig symbol. It can be used as a catch-all dependency to override architectural dependencies of drivers. This allows all drivers marked with this dependency to be built on all architectures (unless excluded via other dependency chains), which means that you can compile-test the code without resorting to cross-compilation or multiple configurations.
If used without care it can be surprisingly painful, though. The problem with the
COMPILE_TEST symbol is that drivers are exposed on architectures and configurations that they normally aren't compile-tested on. Build breakage is a common result of adding
COMPILE_TEST dependencies without careful consideration. The reason is primarily that the architectural dependencies imply the existence of a specific API that may isn't supported on all architectures. Builds on some of the more exotic architectures will trigger this kind of failure, though they've also been known to happen on fairly common configurations.
Perhaps the most popular solution to this is to provide dummy implementations of an API that will allow compilation to succeed on configurations where it isn't available. This cuts both ways, though, because it also means that you get to successfully build a kernel image that includes drivers which will use the dummy implementation and therefore be completely dysfunctional. There are legitimate uses for that, but they are very rare.
Cross-compiling
A more rigorous approach is to use cross-compilation toolchains and build multiple configurations that will ensure full coverage without resorting to fake dependencies.
Using
toolchains,
scripts and configuration that I've written about previously, I perform quick sanity builds for a number of architectures and configurations for several subsystems on a regular basis. Often I will run them on the latest linux-next tree or before pushing code to a public repository.
Maintaining configurations
Sanity builds often rely on
allmodconfig configurations, which enable all drivers on a particular architecture. This is useful because it always gives you the maximum coverage. The downside is that it will require a very long time for such builds to complete. If all you want to do is compile a set of drivers (i.e. all those in a particular subsystem) you can get done much faster.
However, you wouldn't want to keep various
.config files around, because they can become a nightmare to maintain as kernel development progresses. I've been using a method that takes advantage of some of Kconfig's functionality to create a set of minimal configurations that will provide maximum build coverage.
The idea is to create a sort of script for each configuration that will gradually tune the
.config file. Each script starts out by specifying the architecture and will then typically use
allnoconfig as a starting point. Individual symbols can then be enabled as needed. Finally all the changes will be applied and additional dependencies resolved using an
olddefconfig run before the configuration is built. The script language is easily extensible, we'll see shortly why that is, but these are the most common commands:
- include: includes another script
- kconfig: implements a Kconfig frontend with additional subcommands:
- architecture: selects the architecture to build
- allnoconfig, olddefconfig, ...: this is really a wildcard on *config that will run the given configuration target using the Linux kernel's makefile
- enable: enable a Kconfig symbol
- module: enable a Kconfig symbol as module
- disable: disable a Kconfig symbol
- kbuild: build the configuration
An example configuration might look like this:
kconfig architecture x86
kconfig allnoconfig
# basic configuration
kconfig enable DEBUG_FS
kconfig enable SYSFS
# for PWM_CRC
kconfig enable GPIOLIB
kconfig enable I2C
kconfig enable INTEL_SOC_PMIC
# for PWM_LPSS_PCI
kconfig enable PCI
# for PWM_LPSS_PLATFORM
kconfig enable ACPI
# PWM drivers
kconfig enable PWM
kconfig enable PWM_CRC
kconfig enable PWM_LPSS
kconfig enable PWM_LPSS_PCI
kconfig enable PWM_LPSS_PLATFORM
kconfig enable PWM_PCA9685
# PWM users
include users.include
kconfig enable DRM
kconfig enable DRM_I915
kconfig enable MFD_INTEL_SOC_PMIC
kconfig olddefconfig
kbuild
This is one of the configurations I use to compile-test the PWM subsystem. As you can see, this selects the x86 architecture and starts off with an
allnoconfig. It then enables some basic options such as
DEBUG_FS and
SYSFS because they enable optional code. What follows are some sections that enable dependencies for various drivers, the driver options themselves and a set of users. Note how this includes
users.include, a file that contains a set of options that enables users of the PWM API and which is shared with various other configurations. Finally the
olddefconfig command will resolve all dependencies and generate the final
.config file which is used by the
kbuild command to build the kernel image.
An interesting implementation detail is that this script is really a shell script. This has a number of advantages:
- comments are automatically parsed and discarded by the shell
- commands can be implemented simply by shell functions
- the include command is trivial to implement in terms of the source builtin
There are various example scripts that make use of this:
https://github.com/thierryreding/scripts/blob/master/pwm/build
https://github.com/thierryreding/scripts/blob/master/drm/build
Configuration scripts are available in the configs subdirectories of the respective parent directories. If you look at the code you'll note that there's potential for refactoring. Each of the scripts, at the time of this writing, duplicates the implementation of the configuration script commands. I'm likely going to factor this out into a common script library that can be shared.