As a Linux kernel developer, I will often need to build and boot new kernels to hunt down issues or test new functionality for regressions. While it is possible to manually install these kernels on machines, it is easiest to use the distribution’s package manager, as the kernel does not need to be built on the machine it is being installed on. With .deb
and .rpm
-based systems, it is easy to build a kernel package within the kernel source itself, using the bindeb-pkg
and binrpm-pkg
targets respectively. However, for Arch Linux, my distribution of choice, that is not so simple. Furthermore, when doing certain types of development, such as bisecting an issue, it is more convenient to do all the building in an actual source tree, rather than one that is managed by the Arch Build System. The following process might not be the most efficient or optimal way to do this process (the Arch wiki has a whole article about doing a git bisect
with a PKGBUILD
) but it works for me :)
The general idea is to let makepkg
only do the final packaging, which frees us up to run whatever commands we need in order to generate the kernel and modules.
NOTE: This guide assumes some familiarity with the Arch Build System and building a Linux kernel.
1. Build the kernel
For the following example, we will build a mainline Linux kernel. First, grab the kernel source.
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
$ cd linux
Once the source is downloaded, set up the version string file like Arch does in the main linux
PKGBUILD
, which helps with identifying the current kernel running.
$ scripts/setlocalversion --save-scmversion
I recommend adding a suffix, such as -debug
, which will help you make sure you are running the correct kernel.
$ echo -debug >localversion.10-pkgname
Grab the latest Arch Linux configuration to make sure that your kernel will have all the drivers that the stock kernel does. If you are running a stock kernel, it is available at /proc/config.gz
:
$ zcat /proc/config.gz >.config
Otherwise, you can fetch it from the Arch Linux repo
$ curl -LSso .config https://github.com/archlinux/svntogit-packages/raw/packages/linux/trunk/config
Bring the configuration up to date without any input; if you do want to be prompted, use oldconfig
instead of olddefconfig
. There might be some warnings but they should not be problematic.
$ make -j"$(nproc)" olddefconfig
At this point, you can customize the kernel you are about to build by running make menuconfig
. If you want to save some time compiling, consider using make localmodconfig
to only build the modules that your system is doing to run. I recommend installing modprobed-db
and passing in the database it generates like so:
$ make -j"$(nproc)" LSMOD=$HOME/.config/modprobed.db localmodconfig
Next, we dump the kernel version to a file, which we will use in the packaging stage to ensure our files all end up in the correct location.
$ make -s kernelrelease >version
Finally, we will build the kernel, which can take some time depending on your hardware and such.
$ make -j"$(nproc)"
2. Set up PKGBUILD
and packaging space
Once the kernel is all built, we need to set up our PKGBUILD
and packaging area.
I usually maintain the packaging in a random folder on my hard drive. I will be using this random folder throughout the rest of the example, feel free to change it to wherever is convenient for you. For ease of iteration, which I will touch on later, I recommend you maintain this OUTSIDE of the git repository you are working in.
$ pkgroot="$HOME/tmp/linux-pkgbuild"
$ mkdir -p "$pkgroot"
Create "$pkgroot"/PKGBUILD
with your favorite editor. My typical PKGBUILD
for these situations looks like:
pkgname=linux-debug
pkgver=
pkgrel=1
arch=(x86_64)
license=(GPL2)
options=('!strip')
package() {
pkgdesc="The Linux kernel and modules"
depends=(coreutils kmod initramfs)
optdepends=('crda: to set the correct wireless channels of your country'
'linux-firmware: firmware images needed for some devices')
provides=(VIRTUALBOX-GUEST-MODULES WIREGUARD-MODULE)
replaces=(virtualbox-guest-modules-arch wireguard-arch)
local pkgroot="${pkgdir//\/pkg\/$pkgname/}"
rm -rf "$pkgroot"/pkg
cp -rv "$pkgroot"/pkg-ext "$pkgroot"/pkg
}
# vim:set ts=8 sts=2 sw=2 et:
Basically, we are doing to manually package everything in pkg-ext/
, then we are just going to copy that to pkg/
, and let makepkg
do the rest.
3. Package the kernel
Once the packaging folder and PKGBUILD
have been set up, we are ready to put everything together! This is basically taken straight from the Arch Linux linux
PKGBUILD
, check the _package()
function for any updates to this process that might occur down the road.
Set up variable for future use, which ensures everything gets placed in the right location (pkgroot
was set above, linux-debug
might be different depending on your pkgname
choice in your PKGBUILD
):
$ pkg=linux-debug
$ pkgdir=$pkgroot/pkg-ext/$pkg
$ modulesdir="$pkgdir/usr/lib/modules/$(<version)"
Install the kernel:
$ install -Dm644 "$(make -s image_name)" "$modulesdir"/vmlinuz
Set up pkgbase
, which is needed for mkinitcpio
:
$ echo "$pkg" | install -Dm644 /dev/stdin "$modulesdir"/pkgbase
Install modules:
$ make -j"$(nproc)" DEPMOD=/doesnt/exist INSTALL_MOD_PATH="$pkgdir"/usr INSTALL_MOD_STRIP=1 modules_install
Remove build
and source
symlinks:
$ rm "$modulesdir"/{build,source}
Update pkgver
in your PKGBUILD
, which helps ensure you installed the correct kernel:
$ sed -i "s/pkgver=.*/pkgver=$(git describe | sed 's/-/./g')/g" "$pkgroot"/PKGBUILD
Finally, package it up!
$ cd "$pkgroot"
$ makepkg -R
After that, install it with pacman
and configure your bootloader, just like you would as if you were installing an official kernel. Make sure not to remove the stock linux
package, as you might run into issues that requires a revert!
Iteration
This whole process is very easily scriptable. If you need to do this process back to back, I would recommend:
-
Removing
$pkgroot/pkg
,$pkgroot/pkg-ext
, and$pkgroot/*.tar.zst
before building the next kernel, as that will prevent you from accidentally packaging an old kernel (i.e., skipping the third step). -
Run
git clean -fxdq
in thelinux
folder between builds and repeat step 1 fully (aside from the initial clone), which will prevent weird build system cache invalidation issues.
Getting help
I am happy to correct any part of this guide that felt unnecessary or confusing or improve it with any additional information. Feel free to comment below or reach out to me via Twitter or email with any feedback!