Over the last week I’ve been working on a Linux driver for the RTL8723CS wifi chip (the one used in the Pinephone). This involves a lot of reading and writing code, as well as kernel builds. After a bit of fiddling and bugfixing to get them to work, envkernel.sh
and clangd
make this easier and faster for me.
Clangd is an LSP (language server protocol) server for C (and C++, Objective-C, but I don’t use those), it can tell your editor where there are errors in your code, offer autocompletion, refactoring, jump to definitions, etc., lots of helpful things. The catch: If your code does anything more complex than #include
ing a standard header file, you need to tell clangd how it’s supposed to be built using a compile_commands.json
file.
The Linux kernel definitely falls under “more complex”. For a common library you might use a tool like Bear to generate that file. The kernel has a helpful scripts/clang-tools/gen_compile_commands.py
script right in the source tree, but it needs to analyze kernel build output. So far I had been building my kernel using pmbootstrap build --src
(which produces a postmarketOS kernel package), so I didn’t have that at hand.
envkernel.sh
promised to help there, and make my builds smoother in general: Sourcing it sets up a few aliases (e.g. for make
), so the usual Linux build process (with make menuconfig
and make
) runs in the chroot and with the cross-compilers already provided by pmbootstrap. Essentially no extra setup if you use pmbootstrap already.
Setting up envkernel.sh
Sourcing envkernel.sh
isn’t a particular challenge, it just itched me that the documentation said you need to have it in a full pmbootstrap source tree. I had the Debian package already installed and didn’t want to switch. There was an already open issue marked “help wanted”, and the fixes looked simple enough, so I made an MR. Yes, that means I ended up working right from the pmbootstrap source anyway. 😸
Kernel build
Kernel config and make
just worked as described in the postmarketOS wiki, nothing special there.
Compile Commands
I made a mistake here: I assumed I’d have to run gen_compile_commands.py
in the chroot too, using the run-script
alias provided by envkernel.sh
. Turns out it works without that, but I tried run-script scripts/clang-tools/gen_compile_commands.py
first and wondered why I got no output and a compile_commands.json
file containing exactly []
.
I did the obvious thing: The script has a --logging
parameter, so I added --logging DEBUG
and got, to my surprise, exactly the same result: no output, and an empty list. Digging into it I realized that the run-script
alias quietly dropped any parameters to the script being called. The fix I came up with (another MR) isn’t super nice because POSIX shell printf
lacks %q
, but works for my needs.
After I got debug output from gen_compile_commands.py
I understood I needed to tell it where to find the actual build output. The make
alias doesn’t place it right next to the sources (which is the default), but instead into a .output/
subdirectory. So after building your kernel in the envkernel.sh
environment, you need to run:
./scripts/clang-tools/gen_compile_commands.py -d .output/
This should give you a compile_commands.json
file, with one catch: Paths in it might contain /mnt/linux/
(the mount point of the source tree inside the chroot) instead of the actual location of your source tree in a few places. That’s easily fixed with sed
, e.g.:
sed -e s,/mnt/linux/,$(realpath)/,g compile_commands.json
Clangd config
At least on my system clangd didn’t like some GCC compiler flags used in the kernel build, and it needs to be told about cross-compilation. Also clangd somehow likes to assume C headers are C++ (no, I don’t want to #include <iostream>
, thanks). I fixed those errors with .clangd
config:
CompileFlags:
Compiler: clang
Add:
- -xc # C only, no C++
- --target=aarch64
Remove:
- -fno-allow-store-data-races
- -fconserve-stack
- -march=*
- -mabi=*
After telling my editor to restart clangd, it worked. Well, I did it in iterations, but eventually it did. If you replicate it, I recommend you try to work with as few removed options as possible.
Kernel packaging fix
pmbootstrap build
has a --envkernel
option, which packages a kernel built using envkernel.sh
as the matching postmarketOS kernel package. The package depends on your device, with a Pinephone my workflow is:
- Do some programming.
make
pmbootstrap build --envkernel linux-postmarketos-allwinner
pmbootstrap sideload linux-postmarketos-allwinner
The sideload
command just installs the freshly built package on the connected phone, so I’m ready to test.
The problem is that there’s a bug in pmbootstrap which breaks pmbootstrap build --envkernel
for kernel packages that us the downstreamkernel_package
helper in their APKBUILD. That includes linux-postmarketos-allwinner
. So I had to fix that too. That’s the bugfix you actually need if you want to use this workflow with an affected kernel package, the other two are just a fun story.
Summary
If you want to build a kernel with envkernel.sh
, create a compile_commands.json
for clangd from the build, and package the result, you need:
- pmbootstrap (with
envkernel.sh
, so currently from Git). - Build your kernel as the
envkernel.sh
documentation says. ./scripts/clang-tools/gen_compile_commands.py -d .output/
- Replace
/mnt/linux/
with the location of your actual kernel tree incompile_commands.json
, e.g. usingsed
. - To build a postmarketOS package of the kernel you might need this fix for pmbootstrap if your kernel package uses
downstreamkernel_package
(likely).
Happy hacking! 😸