kbuild: add header check facility as a manually run static analyzer

There have been various attempts at adding a header test or check
mechanism in the kernel build system. The header check primarily
consists of ensuring headers are self-contained, have include guards,
and, in some cases, pass kernel-doc.

The main problems have been:

- The dependency tracking creates undesirable artefacts (infamously also
  known as disgusting turds) in the build directory.

- Gating the feature behind a kconfig option is complicated due to
  allyesconfig builds. It's possible, but requires a verbose and
  confusing negative proxy config option.

- Naming the dependency tracking files with a dot prefix or placing them
  in a dot prefixed subdirectory in the build directory to hide them has
  been exceedingly difficult to achieve. (In part due to some Makefiles
  building files in subdirectory hierarchies.)

- The debate which headers, if any, should really be self-contained is
  virtually open-ended.

Approach the problem from a slightly different angle. Add a top level
"headercheck" static analysis target which you have to explicitly run,
and where you have to explicitly state which headers to check:

$ make HEADER_CHECK="(headers|dirs)" headercheck

For example:

$ make HEADER_CHECK="include/drm drivers/gpu/drm/drm_draw_internal.h" headercheck

You can specify multiple path/to/header.h or path/to/dir, relative to
$(srctree), where path/to/dir is recursively scanned for any .h files.

Add a generic %.header-check rule in scripts/Makefile.build to support
this. Note that this rule should *not* be used in, say, driver or
subsystem makefile targets, precisely because of the problems listed
above.

While this still generates the .header-check dependency tracking
artefacts in the build directory, you will only get them as a result of
manually running 'make headercheck', not as part of the regular build.

This approach also allows anyone to check any headers in the tree, with
no makefile modification or kconfig changes required. What you do with
the results is up to you, and the header check facility does not enforce
anything like it would as part of the regular build.

Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Nicolas Schier <nsc@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: Masahiro Yamada <masahiroy@kernel.org>
Cc: linux-kbuild@vger.kernel.org
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
Tested-by: Randy Dunlap <rdunlap@infradead.org>
Acked-by: Randy Dunlap <rdunlap@infradead.org>
Link: https://patch.msgid.link/20260915104331.255636-1-jani.nikula@intel.com
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
This commit is contained in:
Jani Nikula 2026-09-15 13:43:31 +03:00 committed by Nathan Chancellor
parent fd1ef9b669
commit 26d665bef4
No known key found for this signature in database
GPG Key ID: 1D6B269171C01A96
3 changed files with 46 additions and 1 deletions

1
.gitignore vendored
View File

@ -28,6 +28,7 @@
*.gcno
*.gcda
*.gz
*.header-check
*.i
*.ko
*.lex.c

View File

@ -302,7 +302,7 @@ no-dot-config-targets := $(clean-targets) \
run-command
no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \
image_name
single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %/
single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %.header-check %/
config-build :=
mixed-build :=
@ -1544,6 +1544,26 @@ PHONY += scripts_gen_packed_field_checks
scripts_gen_packed_field_checks: scripts_basic
$(Q)$(MAKE) $(build)=scripts scripts/gen_packed_field_checks
# ---------------------------------------------------------------------------
# Header check
# Check the headers in HEADER_CHECK=(headers|dirs)
PHONY += headercheck
ifneq ($(HEADER_CHECK),)
header-check-dirs := $(filter-out %.h,$(HEADER_CHECK))
header-check-files := $(filter %.h,$(HEADER_CHECK)) $(if $(header-check-dirs),$(shell cd $(srctree) && find $(header-check-dirs) -name '*.h' 2>/dev/null))
header-check-targets := $(patsubst %.h,%.header-check,$(sort $(header-check-files)))
headercheck:
$(if $(header-check-targets),,$(error $@ found no headers in HEADER_CHECK="$(HEADER_CHECK)"))
$(Q)$(MAKE) $(header-check-targets)
else
headercheck:
$(error $@ requires HEADER_CHECK=(headers|dirs))
endif
# ---------------------------------------------------------------------------
# Install
@ -1890,6 +1910,8 @@ help:
@echo ' versioncheck - Sanity check on version.h usage'
@echo ' includecheck - Check for duplicate included header files'
@echo ' headerdep - Detect inclusion cycles in headers'
@echo ' headercheck - Check headers in HEADER_CHECK=(headers|dirs) are'
@echo ' self-contained, have header guards, and pass kernel-doc.'
@echo ' coccicheck - Check with Coccinelle'
@echo ' kconfig-sym-check - Check for dangling Kconfig symbol references'
@echo ' clang-analyzer - Check with clang static analyzer'
@ -2255,6 +2277,7 @@ clean: $(clean-dirs)
-o -name '*.ll' \
-o -name '*.gcno' \
-o -name '*.long-type-*.txt' \
-o -name '*.header-check' \
\) -type f -print \
-o -name '.tmp_*' -print \
| xargs rm -rf

View File

@ -306,6 +306,27 @@ quiet_cmd_cc_lst_c = MKLST $@
$(obj)/%.lst: $(obj)/%.c FORCE
$(call if_changed_dep,cc_lst_c)
# Compile C headers (.h) for header check
# ---------------------------------------------------------------------------
# Check a header is self-contained, has header guards, and passes kernel-doc.
#
# This is for 'make HEADER_CHECK=(headers|dirs) headercheck'. Not to be confused
# with CONFIG_UAPI_HEADER_TEST.
#
# Please do *not* plug %.header-check into any configurable targets, unless you
# also figure out a way to completely hide the %.header-check artefacts.
quiet_cmd_header_check = HDRCHK $(patsubst %.header-check,%.h,$@)
cmd_header_check = \
$(CC) $(c_flags) -fsyntax-only -x c /dev/null -include $< -include $<; \
PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none $(KDOCFLAGS) \
$(if $(findstring e, $(KBUILD_EXTRA_WARN))$(CONFIG_WERROR),-Werror) $<; \
touch $@
$(obj)/%.header-check: $(obj)/%.h FORCE
$(call if_changed_dep,header_check)
# Compile Rust sources (.rs)
# ---------------------------------------------------------------------------