I recently pulled down a fresh Linux kernel, excited to compile it myself. Things went well until they didn’t. Right when I hit make
, I was greeted with this cryptic wall of text:
make[1]: *** No rule to make target `kernel/groups.o', needed by `kernel/built-in.o'. Stop.
make: *** [kernel] Error 2
And later, during make install
, I saw another set of angry errors:
ERROR: modinfo: could not find module ipt_MASQUERADE
ERROR: modinfo: could not find module iptable_nat
ERROR: modinfo: could not find module nf_nat
ERROR: modinfo: could not find module freq_table
ERROR: modinfo: could not find module power_meter
ERROR: modinfo: could not find module microcode
At first, it looked like my build was completely broken. But after some trial and error, I figured out why these errors happen and how to fix them. Here’s my story and the clean, reproducible solution I now use.
Nuking Stale Build Artifacts
That mysterious groups.o
error is almost always caused by stale build files. When we change kernel versions, configs, or reuse a dirty build directory, the kernel build system (Kbuild) may try to compile things that don’t exist anymore. That’s why it yells about groups.o
even though it isn’t part of my build.
Clean the source tree and build out-of-tree:
# From the kernel source directory
make mrproper
# Create a fresh build directory
mkdir -p ../linux-build
# Configure (choose one)
make O=../linux-build defconfig # Default config
make O=../linux-build localmodconfig # Based on current system
make O=../linux-build menuconfig # Interactive
# Build and install
make -j"$(nproc)" O=../linux-build
sudo make -j"$(nproc)" O=../linux-build modules
sudo make O=../linux-build modules_install
sudo make O=../linux-build install
Building out-of-tree keeps my kernel sources pristine, and it avoids exactly the kind of dependency mismatch that caused the groups.o
error.
Handling the modinfo
Error
The second problem modinfo: could not find module …
looked scarier than it really was. These errors show up when the distro’s initramfs builder (like dracut
on RHEL or update-initramfs
on Ubuntu) tries to look up modules by name but can’t find the .ko
files.
Two reasons:
- I compiled those features built-in (
=y
), so there was no module file to find. - Or I didn’t enable them at all in my
.config
.
I had three choices:
Enable Missing Features as Modules
If I needed those features as loadable modules, I set them in menuconfig
:
CONFIG_NF_NAT=m
CONFIG_IP_NF_TARGET_MASQUERADE=m
CONFIG_MICROCODE=m
CONFIG_CPU_FREQ_TABLE=m
then rebuilt:
make -j"$(nproc)" O=../linux-build
sudo make O=../linux-build modules_install
sudo make O=../linux-build install
Keep features built in (=y
) and ignore the warnings
If my system booted fine, these warnings were harmless. modinfo
simply can’t query built-ins. The functionality was still present.
All I had to do was regenerate my initramfs and update GRUB:
# On RHEL/CentOS
sudo dracut -f /boot/initramfs-$(make -sO=../linux-build kernelrelease).img $(make -sO=../linux-build kernelrelease)
# On Ubuntu/Debian
sudo update-initramfs -c -k "$(make -sO=../linux-build kernelrelease)"
sudo update-grub
Tell the initramfs builder to skip them
On RHEL, I could drop a config snippet under /etc/dracut.conf.d/
to exclude unneeded modules.
Automating the Process
To make my life easier, I wrote a helper script that does all the cleaning, out-of-tree builds, installs, and even regenerates initramfs and GRUB automatically.
Here’s my kbuild.sh
:
#!/usr/bin/env bash
set -euo pipefail
SRC="${1:-$PWD}" # kernel source
BUILD="${2:-$SRC/../linux-build}" # build dir
TARGET="${3:-defconfig}" # config target
JOBS="${JOBS:-$(nproc)}"
LOGDIR="${BUILD}/_logs"; mkdir -p "$LOGDIR"
echo "[i] Source: $SRC"
echo "[i] Build : $BUILD"
echo "[i] Target: $TARGET"
echo "[i] Jobs : $JOBS"
# Sanity check
command -v make gcc bc perl pahole >/dev/null 2>&1 || {
echo "[!] Missing tools: install build-essential, bc, bison, flex, libssl-dev, dwarves"
exit 1
}
make -C "$SRC" mrproper
mkdir -p "$BUILD"
make -C "$SRC" O="$BUILD" "$TARGET" 2>&1 | tee "$LOGDIR/1_config.log"
make -C "$SRC" O="$BUILD" -j"$JOBS" 2>&1 | tee "$LOGDIR/2_build.log"
sudo make -C "$SRC" O="$BUILD" modules_install 2>&1 | tee "$LOGDIR/3_modinstall.log"
sudo make -C "$SRC" O="$BUILD" install 2>&1 | tee "$LOGDIR/4_install.log"
KREL="$(make -s -C "$SRC" O="$BUILD" kernelrelease)"
echo "[i] Kernel release: $KREL"
if command -v dracut >/dev/null 2>&1; then
sudo dracut -f "/boot/initramfs-${KREL}.img" "${KREL}"
elif command -v update-initramfs >/dev/null 2>&1; then
sudo update-initramfs -c -k "${KREL}"
fi
if command -v grub2-mkconfig >/dev/null 2>&1; then
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
elif command -v update-grub >/dev/null 2>&1; then
sudo update-grub
fi
echo "[✓] Build complete. Reboot into kernel ${KREL}."
Now I just run:
./kbuild.sh /path/to/linux ../build menuconfig
and it takes care of everything.
Final Thoughts
What I learned from this exercise is that kernel builds often fail if I reuse a dirty source tree, so building out-of-tree is the safest approach. The scary modinfo: could not find module
messages aren’t always real errors they usually just mean the feature is built into the kernel rather than a separate module. Writing a small helper script also made the whole process smoother and less frustrating.