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 #includeing 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.
makepmbootstrap build --envkernel linux-postmarketos-allwinnerpmbootstrap 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.shdocumentation 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! 😸



