bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build
@ 2026-08-04 17:01 Mykola Lysenko
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line Mykola Lysenko
                   ` (7 more replies)
  0 siblings, 8 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko


The BPF selftests Makefile has grown to ~1,100 lines, with much of the
complexity concentrated in the DEFINE_TEST_RUNNER double-expansion
machinery: the per-runner rules are written in $$-escaped make and
eval'd once per flavor, so the rules that actually run never appear in
the source, are invisible to make's own debugging facilities, and are
easy to break for one flavor while testing another.

The series opens with five small stand-alone changes, each usable
(or droppable) on its own: patch 1 filters headers off the generic
link rule's command line (gcc tolerates a stray .h there, clang
refuses); patch 2 drops four stale target-specific lines that name
objects nothing builds - redeclaring the two dependencies of value
among them in forms that work (flow_dissector_load.h against the
binary; cgroup_getset_retval_hooks.h against the BPF objects, which
the progs/*.h blanket does not cover) - plus a no-op CURDIR override;
patch 3 factors the eleven identical permissive-mode "remove the
target, print SKIP, report success" recipe tails into a skip_on_fail
helper; patch 4 makes the signing key generation race-free under -j;
patch 5 turns the verifier/tests.h $(shell)-in-recipe into a regular
recipe. The restructure itself is three steps, each independently
buildable and each producing byte-identical artifacts:

  6) the four near-identical skeleton generation recipes move into a
     parameterized helper script (gen_bpf_skel.sh);
  7) shared (non-rule) definitions move into Makefile.buildvars;
  8) each test runner instance (test_progs, test_progs-<flavor>,
     test_maps) becomes its own plain-make sub-make (Makefile.runner),
     replacing the eval/escaping layer.

After the series the top Makefile owns everything that exists once
(toolchain setup, the libbpf/bpftool/resolve_btfids sub-builds,
vmlinux.h, shared helper objects, standalone binaries, and the lib.mk
run/install contract), while every runner instance is an ordinary
single-flavor makefile with no escaping. Total line count is roughly a
wash; the point is the structure, not the size.

Sent as RFC to gather feedback on the overall direction.

Validation (each patch, x86_64, identical toolchains, clean builds):
2,960/2,960 BPF objects and 5,376/5,376 skeleton headers byte-identical
to the current Makefile's output, with identical build-artifact file
lists. For the end state additionally: all 989 userspace objects and
binaries byte-identical except bench and test_maps, which differ only
in object link order, proven by relinking the current Makefile's
objects in the new order and comparing bytes; emit_tests output and
the installed tree identical; BPF CI green on a manual pre-submission
run across x86_64 gcc/llvm, aarch64 and s390x (including GCC-BPF, ASAN
and veristat jobs); and all 76 benchmarks produce identical outcomes
on old- and new-built bench binaries.

v4 -> v5:
- the four stale target-specific lines (test_l4lb_noinline /
  test_xdp_noinline -fno-inline, flow_dissector_load.o /
  cgroup_getset_retval_hooks.o header deps) move out of the runner
  patch into a leading cleanup patch (which also redeclares the
  flow_dissector_load.h and cgroup_getset_retval_hooks.h
  dependencies, the two of the four with value, in working form), preceded by a patch filtering headers off the
  generic link command line so every intermediate state builds with
  either compiler - the series is now 6 patches; per-patch validation
  notes leave the commit messages - validation for every patch is
  summarized above instead.
- the permissive-mode skip suffix becomes the skip_on_fail helper in
  its own patch before the restructure, so the script patch's new
  skeleton recipes never carry the open-coded tails and the buildvars
  patch stays a pure move; the signing-key race fix and the
  verifier/tests.h recipe conversion likewise become their own
  patches instead of riding inside the runner patch.
- runner patch sheds incidental churn: the '# Some utility functions
  use LLVM libraries' comment moves to Makefile.runner together with
  the jit_disasm_helpers CFLAGS line it describes; the notdir
  convenience rule, the generic compile rule, BPF_GCC's stock ?=
  probe and surrounding blank lines are left exactly as they are in
  the current Makefile.
- buildvars patch: moving the srctree fallback and PKG_CONFIG into
  Makefile.buildvars put them after ../../../build/Makefile.feature is
  parsed, which captures both at parse time - in a standalone build
  srctree was empty there, so the LLVM feature probe targeted
  /tools/build/feature, failed with stderr discarded, and feature-llvm
  silently read 0: the jit-disassembler helpers compiled into their
  -EOPNOTSUPP stubs and the __jited verifier tests
  (verifier_private_stack, verifier_jit_inline, verifier_jit_convergence,
  verifier_tailcall_jit) silently flipped to SKIP, confirmed in the v4
  BPF CI logs ("llvm: [ OFF ]" in the feature display). Both now stay
  untouched at their original positions in the Makefile, with guarded
  copies kept in Makefile.buildvars for the runner sub-makes (found by
  the bpf-ci AI reviewer).
- buildvars patch: the changelog now names both evaluation-time changes
  (CFLAGS prepend, CLANG_SYS_INCLUDES immediate assignment) instead of
  claiming a single exception (bpf-ci AI reviewer).
- runner patch: the commit message no longer claims the light-skeleton
  demand lists are derived from #include lines - that derivation was
  dropped in v4; the lists stay hand-maintained (found by Sashiko AI
  review).

- v4: https://lore.kernel.org/bpf/20260724042600.175440-1-nickolay.lysenko@gmail.com/

v3 -> v4:
- fix patch 2: "export BPF_GCC TEST_KMODS" preceded the TEST_KMODS
  definition, and 'export NAME' on an undefined variable creates it
  empty, turning the ?= default into a no-op - the kernel test modules
  were silently neither built nor installed and every BPF CI
  test-runner job failed with "Can't find bpf_testmod.ko" (caught by
  BPF CI; not reproducible locally where TEST_KMODS is overridden).
  The definitions now precede the export, with a comment on the trap.
- addressed Eduard's (partial) review of patch 3: header comment
  trimmed; CHECK_FEAT cosmetic reverted; the stock named helper
  variables and per-binary dependency lines are kept (HELPER_OBJS is
  defined from them); map_tests/tests.h added to the runner
  prerequisites; the include-derived skeleton lists are dropped from
  this series in favor of the current hand-maintained lists (the
  derivation can be a follow-up); the per-flavor BPF compiler
  differences became plain sub-make parameters, removing the runner's
  only conditional compile block; content the series does not change
  (helper variables and dependency lines, docs and resolve_btfids
  rules, uprobe_multi, verifier/tests.h, the libarena targets) keeps
  its original file position, so it appears as context rather than
  churn in the diff
- addressed Eduard's review of patch 2: Makefile.buildvars keeps the
  definitions in the order they had in the original Makefile; knobs
  the runner never reads (SKIP_*, submake_extras, VMLINUX_BTF,
  TEST_KMOD_TARGETS) stay in the main Makefile (TEST_KMOD_TARGETS is
  inlined at its single runner use); patch 2 no longer touches
  TEST_KMODS, TEST_KMOD_TARGETS or the VMLINUX_BTF block at all - they
  keep their current positions, and the export the runners need moves
  to patch 3 next to the LLVM-probe export; file header comments
  trimmed
- addressed Eduard's review of patch 1: the gen_bpf_skel.sh interface
  shrinks to --name/--skel/--subskel/--lskel/--sign - the
  linked/llinked intermediate infix is derived inside the script,
  --sign is a boolean taking the key and certificate from
  $PRIVATE_KEY/$VERIFICATION_CERT in the environment, the build-log
  lines moved into the make recipes ($(call msg,...)), and the unused
  --no-determinism-check escape hatch is gone (the determinism check
  is unconditional)
- v3: https://lore.kernel.org/bpf/20260722040830.387979-1-nickolay.lysenko@gmail.com/

v2 -> v3:
- BPF_GCC and TEST_KMODS are defined once (main Makefile, which needs
  them before lib.mk) and exported to the runner sub-makes instead of
  being duplicated in Makefile.buildvars, where the copies could
  silently drift (reported by Sashiko AI review)
- the permissive-mode runner link rule keeps the current Makefile's
  incremental semantics: test objects existing at parse time are
  normal prerequisites again, so editing a test source relinks the
  runner (reported by Sashiko AI review; v2 had regressed this to
  order-only)
- v2: https://lore.kernel.org/bpf/20260721194111.334795-1-nickolay.lysenko@gmail.com/

v1 -> v2:
- rebased onto current bpf-next: ported the semantics of commit
  0b236ac75d04 ("selftests/bpf: Fix make install target") - bpftool
  installed under tools/sbin/, *.BTF files installed - into the new
  install rule (resolves the CI apply conflict against v1)
- gen_bpf_skel.sh is a prerequisite of every skeleton rule, so editing
  the script regenerates the headers (reported by Sashiko AI review)
- the shared helper objects the top Makefile pre-builds now depend on
  a superset of the prerequisites the runner-side object rule uses
  (libarena skeletons, both generated tests.h headers), closing a
  window where the unflavored test_progs and test_maps sub-makes could
  both consider a shared helper object stale and recompile it
  concurrently in the shared output directory (reported by Sashiko AI
  review)
- v1: https://lore.kernel.org/bpf/20260721174909.8044-1-nickolay.lysenko@gmail.com/

Mykola Lysenko (8):
  selftests/bpf: keep headers off the generic link command line
  selftests/bpf: drop stale and no-op lines
  selftests/bpf: factor the permissive-mode skip suffix into a helper
  selftests/bpf: generate the signing key and certificate once
  selftests/bpf: generate verifier/tests.h in a regular recipe
  selftests/bpf: extract BPF skeleton generation into a helper script
  selftests/bpf: move shared build definitions into Makefile.buildvars
  selftests/bpf: build each test runner instance in its own sub-make

 tools/testing/selftests/bpf/Makefile          | 742 ++++--------------
 .../testing/selftests/bpf/Makefile.buildvars  | 171 ++++
 tools/testing/selftests/bpf/Makefile.runner   | 320 +++++++
 tools/testing/selftests/bpf/gen_bpf_skel.sh   |  93 ++
 4 files changed, 751 insertions(+), 575 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/Makefile.buildvars
 create mode 100644 tools/testing/selftests/bpf/Makefile.runner
 create mode 100755 tools/testing/selftests/bpf/gen_bpf_skel.sh


base-commit: 7f333f85f83da9a20a60a1d8bf518c4ae0818e5a
-- 
2.43.0


^ permalink raw reply	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-05 21:21   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines Mykola Lysenko
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

The generic '$(OUTPUT)/%:%.c' rule links with '$(LINK.c) $^', so
every prerequisite of such a binary lands on the compiler driver's
command line. That only works while none of them is a header: gcc
tolerates a stray .h argument on a link line, but clang treats it as
a request to precompile the header and fails with "cannot specify -o
when generating multiple output files".

Filter headers out of the recipe, with a comment: header
prerequisites on these targets are useful for dependency tracking -
the next patch declares one - and the constraint is easy to
rediscover the hard way.

Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 55d394438..65c702613 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -258,9 +258,12 @@ $(OUTPUT)/%.o: %.c
 	$(call msg,CC,,$@)
 	$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
 
+# Headers may appear among the prerequisites for dependency tracking;
+# they must not reach the clang/gcc command line (clang treats a bare .h
+# argument as a precompiled-header job and refuses -o).
 $(OUTPUT)/%:%.c
 	$(call msg,BINARY,,$@)
-	$(Q)$(LINK.c) $^ $(LDLIBS) -o $@
+	$(Q)$(LINK.c) $(filter-out %.h,$^) $(LDLIBS) -o $@
 
 # LLVM's ld.lld doesn't support all the architectures, so use it only on x86
 ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 riscv))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-05 21:28   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper Mykola Lysenko
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

Four target-specific lines name objects nothing builds. Three went
dead when commit afef88e65554 ("selftests/bpf: Store BPF object files
with .bpf.o extension") renamed the BPF objects; the
flow_dissector_load.o dependency names an intermediate the generic
one-step compile+link rule does not produce.

The '-fno-inline' pair (test_l4lb_noinline, test_xdp_noinline)
applied the flag until the rename; both programs have compiled
without it since, and nothing was lost: every function in them
carries a noinline annotation (the single __always_inline helper in
each is a deliberate exception the flag never overrode), and
compiling with -fno-inline restored yields byte-identical objects.

The two header dependencies with remaining value are redeclared in
working form. flow_dissector_load.h moves to the binary itself, which
is linked straight from its .c by the generic '$(OUTPUT)/%:%.c' rule
- editing the header now rebuilds it. cgroup_getset_retval_hooks.h -
added by commit e7215f574079 ("selftests/bpf: Make sure
bpf_{g,s}et_retval is exposed everywhere") days before the rename
orphaned it - is redeclared against the .bpf.o of every flavor: the
header lives in the top directory, outside the progs/*.h blanket
prerequisite, so editing it never rebuilt the BPF object. (Its
userspace consumer is tracked exactly by the compiler-generated
dependency files.)

'CURDIR := $(abspath .)' redefines make's builtin to the value it
already has; drop it too.

Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 65c702613..69093fba3 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -6,7 +6,6 @@ include ../../../scripts/Makefile.include
 CXX ?= $(CROSS_COMPILE)g++
 OBJCOPY ?= $(CROSS_COMPILE)objcopy
 
-CURDIR := $(abspath .)
 TOOLSDIR := $(abspath ../../..)
 LIBDIR := $(TOOLSDIR)/lib
 BPFDIR := $(LIBDIR)/bpf
@@ -480,11 +479,14 @@ BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)	\
 
 CLANG_CFLAGS = $(CLANG_SYS_INCLUDES)
 
-$(OUTPUT)/test_l4lb_noinline.o: BPF_CFLAGS += -fno-inline
-$(OUTPUT)/test_xdp_noinline.o: BPF_CFLAGS += -fno-inline
+$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
 
-$(OUTPUT)/flow_dissector_load.o: flow_dissector_load.h
-$(OUTPUT)/cgroup_getset_retval_hooks.o: cgroup_getset_retval_hooks.h
+# progs/cgroup_getset_retval_hooks.c includes this top-directory header,
+# which the progs/*.h prerequisite of the BPF object rules does not
+# cover.
+$(addprefix $(OUTPUT)/,cgroup_getset_retval_hooks.bpf.o \
+	$(addsuffix /cgroup_getset_retval_hooks.bpf.o,$(TEST_INST_SUBDIRS))): \
+	cgroup_getset_retval_hooks.h
 
 # Build BPF object using Clang
 # $1 - input .c file
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line Mykola Lysenko
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-04 17:21   ` sashiko-bot
  2026-08-05 21:33   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once Mykola Lysenko
                   ` (4 subsequent siblings)
  7 siblings, 2 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

With BPF_STRICT_BUILD=0, eleven recipes append the same "|| { remove
the target, print a SKIP marker, report success }" tail, each spelled
out inline. Factor the tail into skip_on_fail, parameterized by the
SKIP tag, optional extra files to remove alongside the target and an
optional note appended to the message - every call site keeps its
exact message and behavior.

The permissive fragments of other shapes are not suffixes and keep
their current form: the rsync --ignore-missing-args flags, the
missing-input guards in the skeleton recipes, the link rule's
wildcard handling and the test_kmods copy wrapper.

Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile | 49 +++++++++++-----------------
 1 file changed, 19 insertions(+), 30 deletions(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 69093fba3..565862247 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -49,6 +49,14 @@ SKIP_CRYPTO	?=
 BPF_STRICT_BUILD ?= 1
 PERMISSIVE := $(filter 0,$(BPF_STRICT_BUILD))
 
+# Permissive-mode recipe suffix: on failure, remove the target, emit a
+# SKIP marker and report success so the rest of the build continues.
+# $(1) - SKIP tag (BPF, TEST, BENCH, SKEL, LINK, ...)
+# $(2) - optional extra files to remove alongside the target
+# $(3) - optional note appended to the SKIP message
+skip_on_fail = $(if $(PERMISSIVE),|| { $(RM) $@ $(2); \
+	printf '  %-12s %s\n' 'SKIP-$(1)' '$(notdir $@)$(if $(3), $(3))' 1>&2; })
+
 ifeq ($(srctree),)
 srctree := $(patsubst %/,%,$(dir $(CURDIR)))
 srctree := $(patsubst %/,%,$(dir $(srctree)))
@@ -495,26 +503,22 @@ $(addprefix $(OUTPUT)/,cgroup_getset_retval_hooks.bpf.o \
 # $4 - binary name
 define CLANG_BPF_BUILD_RULE
 	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v3 -o $2 $(if $(PERMISSIVE),|| \
-		($(RM) $2; printf '  %-12s %s\n' 'SKIP-BPF' '$(notdir $2)' 1>&2))
+	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v3 -o $2 $(call skip_on_fail,BPF)
 endef
 # Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
 define CLANG_NOALU32_BPF_BUILD_RULE
 	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v2 -o $2 $(if $(PERMISSIVE),|| \
-		($(RM) $2; printf '  %-12s %s\n' 'SKIP-BPF' '$(notdir $2)' 1>&2))
+	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v2 -o $2 $(call skip_on_fail,BPF)
 endef
 # Similar to CLANG_BPF_BUILD_RULE, but with cpu-v4
 define CLANG_CPUV4_BPF_BUILD_RULE
 	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v4 -o $2 $(if $(PERMISSIVE),|| \
-		($(RM) $2; printf '  %-12s %s\n' 'SKIP-BPF' '$(notdir $2)' 1>&2))
+	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v4 -o $2 $(call skip_on_fail,BPF)
 endef
 # Build BPF object using GCC
 define GCC_BPF_BUILD_RULE
 	$(call msg,GCC-BPF,$4,$2)
-	$(Q)$(BPF_GCC) $3 -DBPF_NO_PRESERVE_ACCESS_INDEX -Wno-attributes -O2 -c $1 -o $2 $(if $(PERMISSIVE),|| \
-		($(RM) $2; printf '  %-12s %s\n' 'SKIP-BPF' '$(notdir $2)' 1>&2))
+	$(Q)$(BPF_GCC) $3 -DBPF_NO_PRESERVE_ACCESS_INDEX -Wno-attributes -O2 -c $1 -o $2 $(call skip_on_fail,BPF)
 endef
 
 SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
@@ -645,10 +649,7 @@ $(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$$(BPFTOOL) gen object $$(<:.o=.linked3.o) $$(<:.o=.linked2.o) && \
 	diff $$(<:.o=.linked2.o) $$(<:.o=.linked3.o) &&		\
 	$$(BPFTOOL) gen skeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.bpf.o=)) > $$@ && \
-	$$(BPFTOOL) gen subskeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.bpf.o=)) > $$(@:.skel.h=.subskel.h) $(if $(PERMISSIVE),|| { \
-		$$(RM) $$@ $$(@:.skel.h=.subskel.h); \
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-	}) && \
+	$$(BPFTOOL) gen subskeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.bpf.o=)) > $$(@:.skel.h=.subskel.h) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h)) && \
 	rm -f $$(<:.o=.linked1.o) $$(<:.o=.linked2.o) $$(<:.o=.linked3.o)
 
 $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
@@ -662,10 +663,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o) && \
 	$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o) && \
 	diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) &&		\
-	$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $(if $(PERMISSIVE),|| { \
-		$$(RM) $$@; \
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-	}) && \
+	$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $$(call skip_on_fail,SKEL) && \
 	rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
 
 $(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
@@ -679,10 +677,7 @@ $(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o) && \
 	$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o) && \
 	diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) &&		\
-	$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $(if $(PERMISSIVE),|| { \
-		$$(RM) $$@; \
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-	}) && \
+	$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $$(call skip_on_fail,SKEL) && \
 	rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
 
 $(LINKED_BPF_OBJS): %: $(TRUNNER_OUTPUT)/%
@@ -704,10 +699,7 @@ $(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_OUTPUT)/%: $$$$(%-deps) $(BPFTOOL) | $(TR
 	diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o) &&	\
 	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2 && \
 	$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@ && \
-	$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h) $(if $(PERMISSIVE),|| { \
-		$$(RM) $$@ $$(@:.skel.h=.subskel.h);			\
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-	}) &&									\
+	$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h)) &&									\
 	rm -f $$(@:.skel.h=.linked1.o) $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
 
 # When the compiler generates a %.d file, only skel basenames (not
@@ -740,8 +732,7 @@ $(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)/%.test.o:			\
 		      $(TRUNNER_TESTS_DIR)/%.c				\
 		      | $(TRUNNER_OUTPUT)/%.test.d
 	$$(call msg,TEST-OBJ,$(TRUNNER_BINARY),$$@)
-	$(Q)(cd $$(@D) && $$(CC) -I. $$(CFLAGS) -MMD -MT $$@ -c $(CURDIR)/$$< $$(LDLIBS) -o $$(@F)) $(if $(filter test_progs%,$1),$(if $(PERMISSIVE),|| \
-		($(RM) $$@; printf '  %-12s %s\n' 'SKIP-TEST' '$$(notdir $$@)' 1>&2)))
+	$(Q)(cd $$(@D) && $$(CC) -I. $$(CFLAGS) -MMD -MT $$@ -c $(CURDIR)/$$< $$(LDLIBS) -o $$(@F)) $(if $(filter test_progs%,$1),$$(call skip_on_fail,TEST))
 	$$(if $$(TEST_NEEDS_BTFIDS),						\
 		$(Q)if [ -f $$@ ]; then						\
 		$(if $(filter 1,$(V)),true,printf '  %-8s%s %s\n' "BTFIDS" " [$(TRUNNER_BINARY)]" "$$(notdir $$@)"); \
@@ -962,8 +953,7 @@ $(OUTPUT)/test_cpp: test_cpp.cpp $(OUTPUT)/test_core_extern.skel.h $(BPFOBJ)
 # Benchmark runner
 $(OUTPUT)/bench_%.o: benchs/bench_%.c bench.h $(BPFOBJ)
 	$(call msg,CC,,$@)
-	$(Q)$(CC) $(CFLAGS) -O2 -c $(filter %.c,$^) $(LDLIBS) -o $@ $(if $(PERMISSIVE),|| \
-		($(RM) $@; printf '  %-12s %s\n' 'SKIP-BENCH' '$(notdir $@)' 1>&2))
+	$(Q)$(CC) $(CFLAGS) -O2 -c $(filter %.c,$^) $(LDLIBS) -o $@ $(call skip_on_fail,BENCH)
 $(OUTPUT)/bench_rename.o: $(OUTPUT)/test_overhead.skel.h
 $(OUTPUT)/bench_trigger.o: $(OUTPUT)/trigger_bench.skel.h
 $(OUTPUT)/bench_ringbufs.o: $(OUTPUT)/ringbuf_bench.skel.h \
@@ -1012,8 +1002,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
 		 $(OUTPUT)/usdt_2.o \
 		 #
 	$(call msg,BINARY,,$@)
-	$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(if $(PERMISSIVE),|| \
-		($(RM) $@; printf '  %-12s %s\n' 'SKIP-LINK' '$(notdir $@) (some benchmarks may have been skipped)' 1>&2))
+	$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(call skip_on_fail,LINK,,(some benchmarks may have been skipped))
 
 # This works around GCC warning about snprintf truncating strings like:
 #
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
                   ` (2 preceding siblings ...)
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-05 22:03   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe Mykola Lysenko
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

$(VERIFICATION_CERT) and $(PRIVATE_KEY) come from a single genkey
invocation, but a rule with two normal targets runs its recipe once
per stale target - under -j both runs execute concurrently and the
openssl invocations race on the same output files. Rewrite it as a
pattern rule: pattern rules with multiple targets are implicitly
grouped even before make 4.3 introduced grouped (&:) targets - the
same workaround the test_kmods rule already uses.

Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 565862247..774286a55 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -800,7 +800,11 @@ VERIFY_SIG_HDR := verification_cert.h
 VERIFICATION_CERT   := $(BUILD_DIR)/signing_key.der
 PRIVATE_KEY := $(BUILD_DIR)/signing_key.pem
 
-$(VERIFICATION_CERT) $(PRIVATE_KEY): $(VERIFY_SIG_SETUP)
+# One genkey run produces both files. A plain two-target rule is not
+# grouped - under -j make would run genkey twice concurrently and the
+# openssl invocations race; the pattern form is implicitly grouped even
+# with make < 4.3.
+$(BUILD_DIR)/%.pem $(BUILD_DIR)/%.der: $(VERIFY_SIG_SETUP)
 	$(Q)mkdir -p $(BUILD_DIR)
 	$(Q)$(VERIFY_SIG_SETUP) genkey $(BUILD_DIR)
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
                   ` (3 preceding siblings ...)
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-05 22:11   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script Mykola Lysenko
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

The verifier/tests.h recipe is a $(shell ...) expansion: the command
runs while make expands the recipe line - including under make -n -
its exit status is discarded, and the resulting (empty) expansion is
what make actually executes. Turn it into a plain recipe.

Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 774286a55..0ee02eeac 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -922,12 +922,12 @@ $(eval $(call DEFINE_TEST_RUNNER,test_maps))
 # them (e.g., test.h is using completely pattern), that it's worth just
 # explicitly defining all the rules explicitly.
 verifier/tests.h: verifier/*.c
-	$(shell ( cd verifier/; \
+	$(Q)( cd verifier/; \
 		  echo '/* Generated header, do not edit */'; \
 		  echo '#ifdef FILL_ARRAY'; \
 		  ls *.c 2> /dev/null | sed -e 's@\(.*\)@#include \"\1\"@'; \
 		  echo '#endif' \
-		) > verifier/tests.h)
+		) > verifier/tests.h
 $(OUTPUT)/test_verifier: test_verifier.c verifier/tests.h $(BPFOBJ) | $(OUTPUT)
 	$(call msg,BINARY,,$@)
 	$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
                   ` (4 preceding siblings ...)
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-04 17:59   ` sashiko-bot
  2026-08-05 22:39   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars Mykola Lysenko
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make Mykola Lysenko
  7 siblings, 2 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

The four skeleton generation recipes (regular, light, signed light and
linked) are near-identical five-step pipelines - link via "bpftool gen
object" three times, compare the second and third results as a
regression test for bpftool's determinism, generate the skeleton (and
possibly subskeleton), remove intermediates - duplicated with small
variations inside DEFINE_TEST_RUNNER_RULES, where every line pays the
double-expansion escaping tax.

Move the pipeline into gen_bpf_skel.sh; the differences between the
four variants become the --lskel, --sign and --subskel flags. Signing
takes the key and certificate from $PRIVATE_KEY and $VERIFICATION_CERT
in the environment, like the bpftool binary comes from $BPFTOOL. The
distinct linked/llinked intermediate infixes are kept but derived
inside the script: tests listed in LSKELS_EXTRA generate both a
.skel.h and a .lskel.h from the same .bpf.o, and distinct intermediate
names are what keeps parallel builds from racing. Build-log messages
stay in the recipes with the usual $(call msg,...) helpers; behavior
is unchanged.

Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
Assisted-by: Claude:claude-fable-5 shellcheck
Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile        | 87 ++++++-------------
 tools/testing/selftests/bpf/gen_bpf_skel.sh | 93 +++++++++++++++++++++
 2 files changed, 119 insertions(+), 61 deletions(-)
 create mode 100755 tools/testing/selftests/bpf/gen_bpf_skel.sh

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 0ee02eeac..dbde2f6bd 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -576,8 +576,6 @@ HEADERS_FOR_BPF_OBJS := $(wildcard $(BPFDIR)/*.bpf.h)		\
 # $1 - test runner base binary name (e.g., test_progs)
 # $2 - test runner extra "flavor" (e.g., no_alu32, cpuv4, bpf_gcc, etc)
 define DEFINE_TEST_RUNNER
-
-LSKEL_SIGN := -S -k $(PRIVATE_KEY) -i $(VERIFICATION_CERT)
 TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2
 TRUNNER_BINARY := $1$(if $2,-)$2
 TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o,	\
@@ -637,70 +635,37 @@ $(TRUNNER_BPF_OBJS): $(TRUNNER_OUTPUT)/%.bpf.o:				\
 					  $$($$<-CFLAGS)		\
 					  $$($$<-$2-CFLAGS),$(TRUNNER_BINARY))
 
-$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
-	$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then			\
-		$$(RM) $$@ $$(@:.skel.h=.subskel.h);			\
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-		exit 0;							\
-	fi;)								\
-	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2; \
-	$$(BPFTOOL) gen object $$(<:.o=.linked1.o) $$< &&		\
-	$$(BPFTOOL) gen object $$(<:.o=.linked2.o) $$(<:.o=.linked1.o) && \
-	$$(BPFTOOL) gen object $$(<:.o=.linked3.o) $$(<:.o=.linked2.o) && \
-	diff $$(<:.o=.linked2.o) $$(<:.o=.linked3.o) &&		\
-	$$(BPFTOOL) gen skeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.bpf.o=)) > $$@ && \
-	$$(BPFTOOL) gen subskeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.bpf.o=)) > $$(@:.skel.h=.subskel.h) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h)) && \
-	rm -f $$(<:.o=.linked1.o) $$(<:.o=.linked2.o) $$(<:.o=.linked3.o)
-
-$(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
-	$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then			\
-		$$(RM) $$@;						\
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-		exit 0;							\
-	fi;)								\
-	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2; \
-	$$(BPFTOOL) gen object $$(<:.o=.llinked1.o) $$< &&		\
-	$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o) && \
-	$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o) && \
-	diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) &&		\
-	$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $$(call skip_on_fail,SKEL) && \
-	rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
-
-$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
-	$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then			\
-		$$(RM) $$@;						\
-		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-		exit 0;							\
-	fi;)								\
-	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY) (signed)] $$(notdir $$@)' 1>&2; \
-	$$(BPFTOOL) gen object $$(<:.o=.llinked1.o) $$< &&		\
-	$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o) && \
-	$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o) && \
-	diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o) &&		\
-	$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@ $$(call skip_on_fail,SKEL) && \
-	rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
+$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
+	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
+	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
+		--name $$(notdir $$(<:.bpf.o=))					\
+		--skel $$@ --subskel $$(@:.skel.h=.subskel.h) $$< $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))
+
+$(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
+	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
+	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
+		--name $$(notdir $$(<:.bpf.o=_lskel)) --lskel			\
+		--skel $$@ $$< $$(call skip_on_fail,SKEL)
+
+$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
+	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY) (signed),$$@)
+	$(Q)BPFTOOL=$$(BPFTOOL) PRIVATE_KEY=$(PRIVATE_KEY)			\
+		VERIFICATION_CERT=$(VERIFICATION_CERT)				\
+		./gen_bpf_skel.sh --sign					\
+		--name $$(notdir $$(<:.bpf.o=_lskel))				\
+		--skel $$@ $$< $$(call skip_on_fail,SKEL)
 
 $(LINKED_BPF_OBJS): %: $(TRUNNER_OUTPUT)/%
 
 # .SECONDEXPANSION here allows to correctly expand %-deps variables as prerequisites
 .SECONDEXPANSION:
-$(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_OUTPUT)/%: $$$$(%-deps) $(BPFTOOL) | $(TRUNNER_OUTPUT)
-	$(Q)$(if $(PERMISSIVE),for f in $$(addprefix $(TRUNNER_OUTPUT)/,$$($$(@F)-deps)); do \
-		if [ ! -f $$$$f ]; then						\
-			$$(RM) $$@ $$(@:.skel.h=.subskel.h);		\
-			printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
-			exit 0;							\
-		fi;								\
-	done;)									\
-	printf '  %-12s %s\n' 'LINK-BPF' '[$(TRUNNER_BINARY)] $$(notdir $$(@:.skel.h=.bpf.o))' 1>&2; \
-	$$(BPFTOOL) gen object $$(@:.skel.h=.linked1.o) $$(addprefix $(TRUNNER_OUTPUT)/,$$($$(@F)-deps)) && \
-	$$(BPFTOOL) gen object $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked1.o) && \
-	$$(BPFTOOL) gen object $$(@:.skel.h=.linked3.o) $$(@:.skel.h=.linked2.o) && \
-	diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o) &&	\
-	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2 && \
-	$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@ && \
-	$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h)) &&									\
-	rm -f $$(@:.skel.h=.linked1.o) $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
+$(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_OUTPUT)/%: $$$$(%-deps) $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
+	$$(call msg,LINK-BPF,$(TRUNNER_BINARY),$$(@:.skel.h=.bpf.o))
+	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
+	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
+		--name $$(notdir $$(@:.skel.h=))				\
+		--skel $$@ --subskel $$(@:.skel.h=.subskel.h)			\
+		$$(addprefix $(TRUNNER_OUTPUT)/,$$($$(@F)-deps)) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))
 
 # When the compiler generates a %.d file, only skel basenames (not
 # full paths) are specified as prerequisites for corresponding %.o
diff --git a/tools/testing/selftests/bpf/gen_bpf_skel.sh b/tools/testing/selftests/bpf/gen_bpf_skel.sh
new file mode 100755
index 000000000..e234f3a57
--- /dev/null
+++ b/tools/testing/selftests/bpf/gen_bpf_skel.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Link BPF object file(s) with "bpftool gen object" and generate a
+# skeleton (or light skeleton) header from the result.
+#
+# Usage:
+#   BPFTOOL=<bpftool> gen_bpf_skel.sh --name NAME --skel OUT [options] OBJ...
+#
+#   --name NAME       skeleton object name ("name NAME" for bpftool)
+#   --skel OUT        output header ("foo.skel.h" or "foo.lskel.h")
+#   --subskel OUT     also generate a subskeleton header into OUT
+#   --lskel           generate a light skeleton (bpftool gen skeleton -L)
+#   --sign            sign the (light) skeleton, with the key and
+#                     certificate taken from $PRIVATE_KEY and
+#                     $VERIFICATION_CERT
+#
+# The inputs are linked three times and the 2nd and 3rd results
+# compared, as a regression test for the determinism of "bpftool gen
+# object".
+#
+# Intermediate files carry a "linked" infix - "llinked" for light or
+# signed skeletons, so generating a .skel.h and a .lskel.h from the
+# same .bpf.o in parallel never races on the intermediates.
+#
+# The bpftool binary is taken from $BPFTOOL (default: bpftool from PATH).
+# On failure all outputs and intermediates are removed and the script
+# exits non-zero; permissive-mode skipping is the caller's business
+# (see skip_on_fail in Makefile.buildvars).
+
+set -u
+
+bpftool=${BPFTOOL:-bpftool}
+name='' skel='' subskel=''
+lskel=0 sign=0
+
+while [ $# -gt 0 ]; do
+	case "$1" in
+	--name)		name=$2; shift 2 ;;
+	--skel)		skel=$2; shift 2 ;;
+	--subskel)	subskel=$2; shift 2 ;;
+	--lskel)	lskel=1; shift ;;
+	--sign)		sign=1; shift ;;
+	--)		shift; break ;;
+	-*)		echo "$0: unknown option: $1" >&2; exit 1 ;;
+	*)		break ;;
+	esac
+done
+
+if [ -z "$name" ] || [ -z "$skel" ] || [ $# -eq 0 ]; then
+	echo "usage: $0 --name NAME --skel OUT [options] OBJ..." >&2
+	exit 1
+fi
+
+infix=linked
+if [ "$lskel" -eq 1 ] || [ "$sign" -eq 1 ]; then
+	infix=llinked
+fi
+
+base=${skel%.skel.h}
+base=${base%.lskel.h}
+t1=$base.${infix}1.o
+t2=$base.${infix}2.o
+t3=$base.${infix}3.o
+
+fail() {
+	rm -f "$skel" ${subskel:+"$subskel"} "$t1" "$t2" "$t3"
+	exit 1
+}
+
+"$bpftool" gen object "$t1" "$@" || fail
+"$bpftool" gen object "$t2" "$t1" || fail
+"$bpftool" gen object "$t3" "$t2" || fail
+if ! cmp -s "$t2" "$t3"; then
+	echo "$0: bpftool gen object is not deterministic for $skel" >&2
+	fail
+fi
+
+args=()
+if [ "$sign" -eq 1 ]; then
+	args+=(-S -k "${PRIVATE_KEY:?}" -i "${VERIFICATION_CERT:?}")
+fi
+if [ "$lskel" -eq 1 ]; then
+	args+=(-L)
+fi
+"$bpftool" gen skeleton ${args[@]+"${args[@]}"} "$t3" name "$name" > "$skel" || fail
+
+if [ -n "$subskel" ]; then
+	"$bpftool" gen subskeleton "$t3" name "$name" > "$subskel" || fail
+fi
+
+rm -f "$t1" "$t2" "$t3"
+exit 0
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
                   ` (5 preceding siblings ...)
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-05 23:34   ` Eduard Zingerman
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make Mykola Lysenko
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

Move the definitions that are not rules into Makefile.buildvars:
output-tree layout, libbpf/bpftool binary locations,
BPF_CFLAGS/COMMON_CFLAGS assembly, libelf/libpcap probes, endianness
and clang feature detection, signing key paths, the permissive-mode
knob with its skip_on_fail helper, and the libarena skeleton names.
This is preparation for building each test runner instance in its own
sub-make: the definitions become includable by more than one
makefile. No rules or recipes are changed; definitions keep their
order.

Two evaluation-time changes are called out in comments: the include
sits after ../lib.mk, so CFLAGS is assembled by *prepending*
COMMON_CFLAGS (keeping the include-search order identical to before,
when the definitions preceded lib.mk's additions), and
CLANG_SYS_INCLUDES becomes an immediate assignment (expanding
get_sys_includes forks several shells; CLANG and CLANG_TARGET_ARCH
are final by this point).

BPF_GCC, TEST_KMODS, the VMLINUX_BTF block and the knobs the runner
never reads (SKIP_*, submake_extras, TEST_KMOD_TARGETS) stay in the
Makefile at their current positions - the first two are needed before
lib.mk is included, where Makefile.buildvars cannot yet be, and reach
the runners through the environment (a later patch exports them). The
srctree fallback and PKG_CONFIG also stay:
../../../build/Makefile.feature captures both at parse time and is
parsed before Makefile.buildvars is included - with srctree still
empty, the LLVM feature probe would fail silently and disable the
jit-disassembler helpers. Makefile.buildvars keeps guarded copies of
both for the runner sub-makes, which never parse Makefile.feature.

Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
Assisted-by: Claude:claude-fable-5 shellcheck
Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile          | 143 +--------------
 .../testing/selftests/bpf/Makefile.buildvars  | 171 ++++++++++++++++++
 2 files changed, 173 insertions(+), 141 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/Makefile.buildvars

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index dbde2f6bd..58c567a22 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -3,60 +3,15 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
-CXX ?= $(CROSS_COMPILE)g++
-OBJCOPY ?= $(CROSS_COMPILE)objcopy
-
-TOOLSDIR := $(abspath ../../..)
-LIBDIR := $(TOOLSDIR)/lib
-BPFDIR := $(LIBDIR)/bpf
-TOOLSINCDIR := $(TOOLSDIR)/include
-TOOLSARCHINCDIR := $(TOOLSDIR)/arch/$(SRCARCH)/include
-BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
-APIDIR := $(TOOLSINCDIR)/uapi
-ifneq ($(O),)
-GENDIR := $(O)/include/generated
-else
-GENDIR := $(abspath ../../../../include/generated)
-endif
-GENHDR := $(GENDIR)/autoconf.h
 PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
 
-ifneq ($(wildcard $(GENHDR)),)
-  GENFLAGS := -DHAVE_GENHDR
-endif
-
 BPF_GCC		?= $(shell command -v bpf-gcc;)
-ifdef ASAN
-SAN_CFLAGS 	?= -fsanitize=address -fno-omit-frame-pointer
-else
-SAN_CFLAGS	?=
-endif
-SAN_LDFLAGS	?= $(SAN_CFLAGS)
-RELEASE		?=
-OPT_FLAGS	?= $(if $(RELEASE),-O2,-O0)
-
-LIBELF_CFLAGS	:= $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
-LIBELF_LIBS	:= $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
 
 SKIP_DOCS	?=
 SKIP_LLVM	?=
 SKIP_LIBBFD	?=
 SKIP_CRYPTO	?=
 
-# When BPF_STRICT_BUILD is 1, any BPF object, skeleton, test object, or
-# benchmark compilation failure is fatal. Set to 0 to tolerate failures
-# and continue building the remaining tests.
-BPF_STRICT_BUILD ?= 1
-PERMISSIVE := $(filter 0,$(BPF_STRICT_BUILD))
-
-# Permissive-mode recipe suffix: on failure, remove the target, emit a
-# SKIP marker and report success so the rest of the build continues.
-# $(1) - SKIP tag (BPF, TEST, BENCH, SKEL, LINK, ...)
-# $(2) - optional extra files to remove alongside the target
-# $(3) - optional note appended to the SKIP message
-skip_on_fail = $(if $(PERMISSIVE),|| { $(RM) $@ $(2); \
-	printf '  %-12s %s\n' 'SKIP-$(1)' '$(notdir $@)$(if $(3), $(3))' 1>&2; })
-
 ifeq ($(srctree),)
 srctree := $(patsubst %/,%,$(dir $(CURDIR)))
 srctree := $(patsubst %/,%,$(dir $(srctree)))
@@ -64,39 +19,15 @@ srctree := $(patsubst %/,%,$(dir $(srctree)))
 srctree := $(patsubst %/,%,$(dir $(srctree)))
 endif
 
-COMMON_CFLAGS = -g $(OPT_FLAGS) -rdynamic -std=gnu11				\
-	  -Wall -Werror -fno-omit-frame-pointer				\
-	  -Wno-unused-but-set-variable					\
-	  $(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS)			\
-	  -I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR)		\
-	  -I$(TOOLSINCDIR) -I$(TOOLSARCHINCDIR) -I$(APIDIR) -I$(OUTPUT)	\
-	  -I$(CURDIR)/libarena/include
-LDFLAGS += $(SAN_LDFLAGS)
-LDLIBS += $(LIBELF_LIBS) -lz -lrt -lpthread
-
-PCAP_CFLAGS	:= $(shell $(PKG_CONFIG) --cflags libpcap 2>/dev/null && echo "-DTRAFFIC_MONITOR=1")
-PCAP_LIBS	:= $(shell $(PKG_CONFIG) --libs libpcap 2>/dev/null)
-LDLIBS += $(PCAP_LIBS)
-CFLAGS += $(COMMON_CFLAGS) $(PCAP_CFLAGS)
-
 # Some utility functions use LLVM libraries
 jit_disasm_helpers.c-CFLAGS = $(LLVM_CFLAGS)
 
-ifneq ($(LLVM),)
-# Silence some warnings when compiled with clang
-CFLAGS += -Wno-unused-command-line-argument
-endif
 
 # Check whether bpf cpu=v4 is supported or not by clang
 ifneq ($(shell $(CLANG) --target=bpf -mcpu=help 2>&1 | grep 'v4'),)
 CLANG_CPUV4 := 1
 endif
 
-# Check whether clang supports BPF address sanitizer (requires LLVM 22+)
-CLANG_HAS_ARENA_ASAN := $(shell echo 'int x;' | \
-	$(CLANG) --target=bpf -fsanitize=kernel-address \
-	-mllvm -asan-shadow-addr-space=1 \
-	-x c -c - -o /dev/null 2>/dev/null && echo 1)
 
 # Order correspond to 'make run_tests' order
 TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_progs \
@@ -220,21 +151,8 @@ ifeq ($(feature-llvm),1)
 endif
 endif
 
-SCRATCH_DIR := $(OUTPUT)/tools
-BUILD_DIR := $(SCRATCH_DIR)/build
-INCLUDE_DIR := $(SCRATCH_DIR)/include
-BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
-ifneq ($(CROSS_COMPILE),)
-HOST_BUILD_DIR		:= $(BUILD_DIR)/host
-HOST_SCRATCH_DIR	:= $(OUTPUT)/host-tools
-HOST_INCLUDE_DIR	:= $(HOST_SCRATCH_DIR)/include
-else
-HOST_BUILD_DIR		:= $(BUILD_DIR)
-HOST_SCRATCH_DIR	:= $(SCRATCH_DIR)
-HOST_INCLUDE_DIR	:= $(INCLUDE_DIR)
-endif
-HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
-RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
+include Makefile.buildvars
+
 VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux)				\
 		     $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)	\
 		     ../../../../vmlinux				\
@@ -324,16 +242,6 @@ $(TEST_KMOD_TARGETS): $(addprefix test_kmods/,$(TEST_KMODS))
 	$(Q)$(if $(PERMISSIVE),if [ -f test_kmods/$(@F) ]; then )cp test_kmods/$(@F) $@$(if $(PERMISSIVE),; fi)
 
 
-DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
-ifneq ($(CROSS_COMPILE),)
-CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
-TRUNNER_BPFTOOL := $(CROSS_BPFTOOL)
-USE_BOOTSTRAP := ""
-else
-TRUNNER_BPFTOOL := $(DEFAULT_BPFTOOL)
-USE_BOOTSTRAP := "bootstrap/"
-endif
-
 $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ)
 
 TESTING_HELPERS	:= $(OUTPUT)/testing_helpers.o
@@ -354,7 +262,6 @@ $(OUTPUT)/test_maps: $(TESTING_HELPERS)
 $(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
 $(OUTPUT)/xsk.o: $(BPFOBJ)
 
-BPFTOOL ?= $(DEFAULT_BPFTOOL)
 $(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile)    \
 		    $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
 	$(Q)$(MAKE) $(submake_extras)  -C $(BPFTOOLDIR)			       \
@@ -447,45 +354,6 @@ $(RESOLVE_BTFIDS): $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/resolve_btfids	\
 		HOSTPKG_CONFIG='$(PKG_CONFIG)' \
 		OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ BPFOBJ=$(HOST_BPFOBJ)
 
-# Get Clang's default includes on this system, as opposed to those seen by
-# '--target=bpf'. This fixes "missing" files on some architectures/distros,
-# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
-#
-# Use '-idirafter': Don't interfere with include mechanics except where the
-# build would have failed anyways.
-define get_sys_includes
-$(shell $(1) $(2) -v -E - </dev/null 2>&1 \
-	| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
-$(shell $(1) $(2) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') \
-$(shell $(1) $(2) -dM -E - </dev/null | grep '__loongarch_grlen ' | awk '{printf("-D__BITS_PER_LONG=%d", $$3)}') \
-$(shell $(1) $(2) -dM -E - </dev/null | grep -E 'MIPS(EL|EB)|_MIPS_SZ(PTR|LONG) |_MIPS_SIM |_ABI(O32|N32|64) ' | awk '{printf("-D%s=%s ", $$2, $$3)}')
-endef
-
-# Determine target endianness.
-IS_LITTLE_ENDIAN := $(shell $(CC) -dM -E - </dev/null | \
-			grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
-MENDIAN:=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian)
-BPF_TARGET_ENDIAN:=$(if $(IS_LITTLE_ENDIAN),--target=bpfel,--target=bpfeb)
-
-ifneq ($(CROSS_COMPILE),)
-CLANG_TARGET_ARCH = --target=$(notdir $(CROSS_COMPILE:%-=%))
-endif
-
-CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH))
-BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)	\
-	     -I$(INCLUDE_DIR) -I$(CURDIR) -I$(APIDIR)			\
-	     -I$(CURDIR)/libarena/include				\
-	     -I$(abspath $(OUTPUT)/../usr/include)			\
-	     -std=gnu11		 					\
-	     -fno-strict-aliasing 					\
-	     -Wno-microsoft-anon-tag					\
-	     -fms-extensions						\
-	     -Wno-compare-distinct-pointer-types			\
-	     -Wno-initializer-overrides					\
-	     #
-# TODO: enable me -Wsign-compare
-
-CLANG_CFLAGS = $(CLANG_SYS_INCLUDES)
 
 $(OUTPUT)/flow_dissector_load: flow_dissector_load.h
 
@@ -760,10 +628,6 @@ $(OUTPUT)/$(TRUNNER_BINARY): $(if $(filter test_progs%,$1),$(if $(PERMISSIVE),$$
 
 endef
 
-VERIFY_SIG_SETUP := $(CURDIR)/verify_sig_setup.sh
-VERIFY_SIG_HDR := verification_cert.h
-VERIFICATION_CERT   := $(BUILD_DIR)/signing_key.der
-PRIVATE_KEY := $(BUILD_DIR)/signing_key.pem
 
 # One genkey run produces both files. A plain two-target rule is not
 # grouped - under -j make would run genkey twice concurrently and the
@@ -798,14 +662,11 @@ LIBARENA_BPF_DEPS := $(wildcard libarena/Makefile		\
 				 libarena/selftests/*		\
 				 libarena/*.bpf.o)
 
-LIBARENA_SKEL := libarena/libarena.skel.h
 
 $(LIBARENA_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
 	+$(MAKE) -C libarena libarena.skel.h $(LIBARENA_MAKE_ARGS)
 
 ifneq ($(CLANG_HAS_ARENA_ASAN),)
-LIBARENA_ASAN_SKEL := libarena/libarena_asan.skel.h
-CFLAGS += -DHAS_BPF_ARENA_ASAN
 
 $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
 	+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
diff --git a/tools/testing/selftests/bpf/Makefile.buildvars b/tools/testing/selftests/bpf/Makefile.buildvars
new file mode 100644
index 000000000..044e0d73b
--- /dev/null
+++ b/tools/testing/selftests/bpf/Makefile.buildvars
@@ -0,0 +1,171 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Shared toolchain, path and flag definitions for the BPF selftests
+# build, included by both Makefile and Makefile.runner. Definitions
+# follow the order they had in the Makefile before the split.
+
+CXX ?= $(CROSS_COMPILE)g++
+OBJCOPY ?= $(CROSS_COMPILE)objcopy
+
+TOOLSDIR := $(abspath ../../..)
+LIBDIR := $(TOOLSDIR)/lib
+BPFDIR := $(LIBDIR)/bpf
+TOOLSINCDIR := $(TOOLSDIR)/include
+TOOLSARCHINCDIR := $(TOOLSDIR)/arch/$(SRCARCH)/include
+BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
+APIDIR := $(TOOLSINCDIR)/uapi
+ifneq ($(O),)
+GENDIR := $(O)/include/generated
+else
+GENDIR := $(abspath ../../../../include/generated)
+endif
+GENHDR := $(GENDIR)/autoconf.h
+PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+
+ifneq ($(wildcard $(GENHDR)),)
+  GENFLAGS := -DHAVE_GENHDR
+endif
+
+ifdef ASAN
+SAN_CFLAGS 	?= -fsanitize=address -fno-omit-frame-pointer
+else
+SAN_CFLAGS	?=
+endif
+SAN_LDFLAGS	?= $(SAN_CFLAGS)
+RELEASE		?=
+OPT_FLAGS	?= $(if $(RELEASE),-O2,-O0)
+
+LIBELF_CFLAGS	:= $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
+LIBELF_LIBS	:= $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
+
+# When BPF_STRICT_BUILD is 1, any BPF object, skeleton, test object, or
+# benchmark compilation failure is fatal. Set to 0 to tolerate failures
+# and continue building the remaining tests.
+BPF_STRICT_BUILD ?= 1
+PERMISSIVE := $(filter 0,$(BPF_STRICT_BUILD))
+
+# Permissive-mode recipe suffix: on failure, remove the target, emit a
+# SKIP marker and report success so the rest of the build continues.
+# $(1) - SKIP tag (BPF, TEST, BENCH, SKEL, LINK, ...)
+# $(2) - optional extra files to remove alongside the target
+# $(3) - optional note appended to the SKIP message
+skip_on_fail = $(if $(PERMISSIVE),|| { $(RM) $@ $(2); \
+	printf '  %-12s %s\n' 'SKIP-$(1)' '$(notdir $@)$(if $(3), $(3))' 1>&2; })
+
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(CURDIR)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+endif
+
+COMMON_CFLAGS = -g $(OPT_FLAGS) -rdynamic -std=gnu11				\
+	  -Wall -Werror -fno-omit-frame-pointer				\
+	  -Wno-unused-but-set-variable					\
+	  $(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS)			\
+	  -I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR)		\
+	  -I$(TOOLSINCDIR) -I$(TOOLSARCHINCDIR) -I$(APIDIR) -I$(OUTPUT)	\
+	  -I$(CURDIR)/libarena/include
+LDFLAGS += $(SAN_LDFLAGS)
+LDLIBS += $(LIBELF_LIBS) -lz -lrt -lpthread
+
+PCAP_CFLAGS	:= $(shell $(PKG_CONFIG) --cflags libpcap 2>/dev/null && echo "-DTRAFFIC_MONITOR=1")
+PCAP_LIBS	:= $(shell $(PKG_CONFIG) --libs libpcap 2>/dev/null)
+LDLIBS += $(PCAP_LIBS)
+# Unlike the pre-split Makefile, this file is included after ../lib.mk
+# (top level) or without it (runner), so COMMON_CFLAGS is prepended to
+# preserve the original include-search order.
+LIB_MK_CFLAGS := $(CFLAGS)
+CFLAGS = $(COMMON_CFLAGS) $(PCAP_CFLAGS) $(LIB_MK_CFLAGS)
+
+ifneq ($(LLVM),)
+# Silence some warnings when compiled with clang
+CFLAGS += -Wno-unused-command-line-argument
+endif
+
+# Check whether clang supports BPF address sanitizer (requires LLVM 22+)
+CLANG_HAS_ARENA_ASAN := $(shell echo 'int x;' | \
+	$(CLANG) --target=bpf -fsanitize=kernel-address \
+	-mllvm -asan-shadow-addr-space=1 \
+	-x c -c - -o /dev/null 2>/dev/null && echo 1)
+
+SCRATCH_DIR := $(OUTPUT)/tools
+BUILD_DIR := $(SCRATCH_DIR)/build
+INCLUDE_DIR := $(SCRATCH_DIR)/include
+BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
+ifneq ($(CROSS_COMPILE),)
+HOST_BUILD_DIR		:= $(BUILD_DIR)/host
+HOST_SCRATCH_DIR	:= $(OUTPUT)/host-tools
+HOST_INCLUDE_DIR	:= $(HOST_SCRATCH_DIR)/include
+else
+HOST_BUILD_DIR		:= $(BUILD_DIR)
+HOST_SCRATCH_DIR	:= $(SCRATCH_DIR)
+HOST_INCLUDE_DIR	:= $(INCLUDE_DIR)
+endif
+HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
+RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
+
+DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
+ifneq ($(CROSS_COMPILE),)
+CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
+TRUNNER_BPFTOOL := $(CROSS_BPFTOOL)
+USE_BOOTSTRAP := ""
+else
+TRUNNER_BPFTOOL := $(DEFAULT_BPFTOOL)
+USE_BOOTSTRAP := "bootstrap/"
+endif
+
+BPFTOOL ?= $(DEFAULT_BPFTOOL)
+
+# Get Clang's default includes on this system, as opposed to those seen by
+# '--target=bpf'. This fixes "missing" files on some architectures/distros,
+# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
+#
+# Use '-idirafter': Don't interfere with include mechanics except where the
+# build would have failed anyways.
+define get_sys_includes
+$(shell $(1) $(2) -v -E - </dev/null 2>&1 \
+	| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__loongarch_grlen ' | awk '{printf("-D__BITS_PER_LONG=%d", $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep -E 'MIPS(EL|EB)|_MIPS_SZ(PTR|LONG) |_MIPS_SIM |_ABI(O32|N32|64) ' | awk '{printf("-D%s=%s ", $$2, $$3)}')
+endef
+
+# Determine target endianness.
+IS_LITTLE_ENDIAN := $(shell $(CC) -dM -E - </dev/null | \
+			grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
+MENDIAN:=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian)
+BPF_TARGET_ENDIAN:=$(if $(IS_LITTLE_ENDIAN),--target=bpfel,--target=bpfeb)
+
+ifneq ($(CROSS_COMPILE),)
+CLANG_TARGET_ARCH = --target=$(notdir $(CROSS_COMPILE:%-=%))
+endif
+
+# Immediate on purpose: expanding get_sys_includes forks several shells.
+CLANG_SYS_INCLUDES := $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH))
+BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)	\
+	     -I$(INCLUDE_DIR) -I$(CURDIR) -I$(APIDIR)			\
+	     -I$(CURDIR)/libarena/include				\
+	     -I$(abspath $(OUTPUT)/../usr/include)			\
+	     -std=gnu11		 					\
+	     -fno-strict-aliasing 					\
+	     -Wno-microsoft-anon-tag					\
+	     -fms-extensions						\
+	     -Wno-compare-distinct-pointer-types			\
+	     -Wno-initializer-overrides					\
+	     #
+# TODO: enable me -Wsign-compare
+
+CLANG_CFLAGS = $(CLANG_SYS_INCLUDES)
+
+VERIFY_SIG_SETUP := $(CURDIR)/verify_sig_setup.sh
+VERIFY_SIG_HDR := verification_cert.h
+VERIFICATION_CERT   := $(BUILD_DIR)/signing_key.der
+PRIVATE_KEY := $(BUILD_DIR)/signing_key.pem
+
+LIBARENA_SKEL := libarena/libarena.skel.h
+
+ifneq ($(CLANG_HAS_ARENA_ASAN),)
+LIBARENA_ASAN_SKEL := libarena/libarena_asan.skel.h
+CFLAGS += -DHAS_BPF_ARENA_ASAN
+endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make
  2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
                   ` (6 preceding siblings ...)
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars Mykola Lysenko
@ 2026-08-04 17:01 ` Mykola Lysenko
  2026-08-06  8:02   ` Eduard Zingerman
  7 siblings, 1 reply; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-04 17:01 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, martin.lau, song, yonghong.song,
	jolsa, memxor, nickolay.lysenko

Replace the DEFINE_TEST_RUNNER/DEFINE_TEST_RUNNER_RULES double-expansion
machinery with Makefile.runner: one sub-make invocation per test runner
instance (test_progs, test_progs-no_alu32, test_progs-cpuv4,
test_progs-bpf_gcc, test_maps), each in its own single-flavor namespace
written in plain make - no $$-escaping, no per-flavor eval guards, no
accumulating vpath directives.

The main Makefile keeps everything that exists once - tool sub-builds,
vmlinux.h, signing keys, tests.h generation, shared helper objects,
standalone binaries and the kselftest lib.mk contract - and delegates to
the runner through explicit per-instance rules. The light-skeleton
demand lists stay hand-maintained as before; the bench object list is
derived from $(wildcard benchs/bench_*.c). The $(OUTPUT) directory
rule, previously generated inside the deleted define, joins MAKE_DIRS.

This also fixes a latent parallel-build race (objects including libbpf
internal headers now order against the bpftool sub-build that installs
them).

The shared helper objects the main Makefile pre-builds depend on a
superset of the prerequisites the runner's uniform object rule uses,
so the unflavored test_progs and test_maps sub-makes always see them
as up to date and never race to recompile them in the shared output
directory.

In permissive mode the runner link rule keeps the current Makefile's
semantics: test objects existing at parse time are normal
prerequisites (editing a test source relinks the runner), the full
set stays order-only to drive the build attempts, and the recipe
links the wildcard survivors.

BPF_GCC and TEST_KMODS are exported next to the LLVM probe results:
the runner sub-makes read both from the environment.

The BPF compiler differences between flavors (clang vs bpf-gcc,
-mcpu level, extra defines and system includes) are plain parameters
on the sub-make invocation, so the runner has a single BPF
compilation rule with no conditionals.

Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
Assisted-by: Claude:claude-fable-5 shellcheck
Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
---
 tools/testing/selftests/bpf/Makefile        | 532 ++++++--------------
 tools/testing/selftests/bpf/Makefile.runner | 320 ++++++++++++
 2 files changed, 470 insertions(+), 382 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/Makefile.runner

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 58c567a22..af3709d51 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -19,9 +19,6 @@ srctree := $(patsubst %/,%,$(dir $(srctree)))
 srctree := $(patsubst %/,%,$(dir $(srctree)))
 endif
 
-# Some utility functions use LLVM libraries
-jit_disasm_helpers.c-CFLAGS = $(LLVM_CFLAGS)
-
 
 # Check whether bpf cpu=v4 is supported or not by clang
 ifneq ($(shell $(CLANG) --target=bpf -mcpu=help 2>&1 | grep 'v4'),)
@@ -40,16 +37,6 @@ TEST_INST_SUBDIRS := no_alu32
 ifneq ($(BPF_GCC),)
 TEST_GEN_PROGS += test_progs-bpf_gcc
 TEST_INST_SUBDIRS += bpf_gcc
-
-# The following tests contain C code that, although technically legal,
-# triggers GCC warnings that cannot be disabled: declaration of
-# anonymous struct types in function parameter lists.
-progs/btf_dump_test_case_bitfields.c-bpf_gcc-CFLAGS := -Wno-error
-progs/btf_dump_test_case_namespacing.c-bpf_gcc-CFLAGS := -Wno-error
-progs/btf_dump_test_case_packing.c-bpf_gcc-CFLAGS := -Wno-error
-progs/btf_dump_test_case_padding.c-bpf_gcc-CFLAGS := -Wno-error
-progs/btf_dump_test_case_syntax.c-bpf_gcc-CFLAGS := -Wno-error
-
 endif
 
 ifneq ($(CLANG_CPUV4),)
@@ -171,7 +158,7 @@ $(notdir $(TEST_GEN_PROGS) $(TEST_KMODS)				\
 	 $(TEST_GEN_PROGS_EXTENDED)): %: $(OUTPUT)/% ;
 
 # sort removes libbpf duplicates when not cross-building
-MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf	\
+MAKE_DIRS := $(sort $(OUTPUT) $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \
 	       $(BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/bpftool		\
 	       $(HOST_BUILD_DIR)/resolve_btfids				\
 	       $(INCLUDE_DIR))
@@ -262,6 +249,35 @@ $(OUTPUT)/test_maps: $(TESTING_HELPERS)
 $(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
 $(OUTPUT)/xsk.o: $(BPFOBJ)
 
+# All helper objects a runner instance links, pre-built here so runner
+# sub-makes sharing $(OUTPUT) never compile them concurrently.
+HELPER_OBJS := $(TESTING_HELPERS) $(CGROUP_HELPERS) $(UNPRIV_HELPERS)	\
+	       $(TRACE_HELPERS) $(JSON_WRITER) $(CAP_HELPERS)		\
+	       $(NETWORK_HELPERS) $(OUTPUT)/usdt_1.o $(OUTPUT)/usdt_2.o	\
+	       $(OUTPUT)/xsk.o
+
+# $(BPFTOOL) is a prerequisite because its sub-make is what installs
+# libbpf's internal headers (bpf/hashmap.h, bpf/libbpf_internal.h) into
+# $(INCLUDE_DIR); without it, helpers including those headers race the
+# install and can silently pick up the source-tree copies instead.
+#
+# Invariant: this list must be a superset of every prerequisite the
+# runner's EXTRA_OBJS rule attaches to these same objects (EXTRA_HDRS,
+# both tests.h flavors, $(BPFOBJ)). It guarantees the helpers built
+# here are never older than a runner-side prerequisite, so the
+# unflavored test_progs and test_maps sub-makes, which share $(OUTPUT),
+# treat them as up to date instead of both recompiling the same object
+# file concurrently.
+$(HELPER_OBJS): flow_dissector_load.h ip_check_defrag_frags.h		\
+		$(VERIFY_SIG_HDR) $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)\
+		prog_tests/tests.h map_tests/tests.h $(BPFOBJ) $(BPFTOOL)
+
+$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
+
+# Tool sub-builds: bpftool, libbpf (host and, when cross-compiling,
+# target variants) and resolve_btfids. Deliberate asymmetry: the host
+# libbpf builds without sanitizers and with the host toolchain.
+
 $(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile)    \
 		    $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
 	$(Q)$(MAKE) $(submake_extras)  -C $(BPFTOOLDIR)			       \
@@ -329,7 +345,7 @@ endif
 
 # vmlinux.h is first dumped to a temporary file and then compared to
 # the previous version. This helps to avoid unnecessary re-builds of
-# $(TRUNNER_BPF_OBJS)
+# BPF objects.
 $(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
 ifeq ($(VMLINUX_H),)
 	$(call msg,GEN,,$@)
@@ -354,281 +370,6 @@ $(RESOLVE_BTFIDS): $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/resolve_btfids	\
 		HOSTPKG_CONFIG='$(PKG_CONFIG)' \
 		OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ BPFOBJ=$(HOST_BPFOBJ)
 
-
-$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
-
-# progs/cgroup_getset_retval_hooks.c includes this top-directory header,
-# which the progs/*.h prerequisite of the BPF object rules does not
-# cover.
-$(addprefix $(OUTPUT)/,cgroup_getset_retval_hooks.bpf.o \
-	$(addsuffix /cgroup_getset_retval_hooks.bpf.o,$(TEST_INST_SUBDIRS))): \
-	cgroup_getset_retval_hooks.h
-
-# Build BPF object using Clang
-# $1 - input .c file
-# $2 - output .o file
-# $3 - CFLAGS
-# $4 - binary name
-define CLANG_BPF_BUILD_RULE
-	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v3 -o $2 $(call skip_on_fail,BPF)
-endef
-# Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
-define CLANG_NOALU32_BPF_BUILD_RULE
-	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v2 -o $2 $(call skip_on_fail,BPF)
-endef
-# Similar to CLANG_BPF_BUILD_RULE, but with cpu-v4
-define CLANG_CPUV4_BPF_BUILD_RULE
-	$(call msg,CLNG-BPF,$4,$2)
-	$(Q)$(CLANG) $3 -O2 $(BPF_TARGET_ENDIAN) -c $1 -mcpu=v4 -o $2 $(call skip_on_fail,BPF)
-endef
-# Build BPF object using GCC
-define GCC_BPF_BUILD_RULE
-	$(call msg,GCC-BPF,$4,$2)
-	$(Q)$(BPF_GCC) $3 -DBPF_NO_PRESERVE_ACCESS_INDEX -Wno-attributes -O2 -c $1 -o $2 $(call skip_on_fail,BPF)
-endef
-
-SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
-
-LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h		\
-		linked_vars.skel.h linked_maps.skel.h 			\
-		test_subskeleton.skel.h test_subskeleton_lib.skel.h	\
-		test_usdt.skel.h tracing_multi.skel.h			\
-		tracing_multi_module.skel.h				\
-		tracing_multi_intersect.skel.h				\
-		tracing_multi_session.skel.h
-
-LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c 	\
-	core_kern.c core_kern_overflow.c test_ringbuf.c			\
-	test_ringbuf_n.c test_ringbuf_map_key.c test_ringbuf_write.c    \
-	test_ringbuf_overwrite.c
-
-LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c
-
-# Generate both light skeleton and libbpf skeleton for these
-LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \
-	kfunc_call_test_subprog.c
-SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED)
-
-test_static_linked.skel.h-deps := test_static_linked1.bpf.o test_static_linked2.bpf.o
-linked_funcs.skel.h-deps := linked_funcs1.bpf.o linked_funcs2.bpf.o
-linked_vars.skel.h-deps := linked_vars1.bpf.o linked_vars2.bpf.o
-linked_maps.skel.h-deps := linked_maps1.bpf.o linked_maps2.bpf.o
-# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
-# but that's created as a side-effect of the skel.h generation.
-test_subskeleton.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o test_subskeleton.bpf.o
-test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o
-test_usdt.skel.h-deps := test_usdt.bpf.o test_usdt_multispec.bpf.o
-xsk_xdp_progs.skel.h-deps := xsk_xdp_progs.bpf.o
-xdp_hw_metadata.skel.h-deps := xdp_hw_metadata.bpf.o
-xdp_features.skel.h-deps := xdp_features.bpf.o
-tracing_multi.skel.h-deps := tracing_multi_attach.bpf.o tracing_multi_check.bpf.o
-tracing_multi_module.skel.h-deps := tracing_multi_attach_module.bpf.o tracing_multi_check.bpf.o
-tracing_multi_intersect.skel.h-deps := tracing_multi_intersect_attach.bpf.o tracing_multi_check.bpf.o
-tracing_multi_session.skel.h-deps := tracing_multi_session_attach.bpf.o tracing_multi_check.bpf.o
-
-LINKED_BPF_OBJS := $(foreach skel,$(LINKED_SKELS),$($(skel)-deps))
-LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.c,$(LINKED_BPF_OBJS))
-
-HEADERS_FOR_BPF_OBJS := $(wildcard $(BPFDIR)/*.bpf.h)		\
-			$(wildcard $(CURDIR)/libarena/include/*.[ch])	\
-			$(addprefix $(BPFDIR)/,	bpf_core_read.h	\
-			                        bpf_endian.h	\
-						bpf_helpers.h	\
-			                        bpf_tracing.h)
-
-# Set up extra TRUNNER_XXX "temporary" variables in the environment (relies on
-# $eval()) and pass control to DEFINE_TEST_RUNNER_RULES.
-# Parameters:
-# $1 - test runner base binary name (e.g., test_progs)
-# $2 - test runner extra "flavor" (e.g., no_alu32, cpuv4, bpf_gcc, etc)
-define DEFINE_TEST_RUNNER
-TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2
-TRUNNER_BINARY := $1$(if $2,-)$2
-TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o,	\
-				 $$(notdir $$(wildcard $(TRUNNER_TESTS_DIR)/*.c)))
-TRUNNER_EXTRA_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o,		\
-				 $$(filter %.c,$(TRUNNER_EXTRA_SOURCES)))
-TRUNNER_LIB_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o,		\
-				 $$(filter %.c,$(TRUNNER_LIB_SOURCES)))
-TRUNNER_EXTRA_HDRS := $$(filter %.h,$(TRUNNER_EXTRA_SOURCES))
-TRUNNER_TESTS_HDR := $(TRUNNER_TESTS_DIR)/tests.h
-TRUNNER_BPF_SRCS := $$(notdir $$(wildcard $(TRUNNER_BPF_PROGS_DIR)/*.c))
-TRUNNER_BPF_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.bpf.o, $$(TRUNNER_BPF_SRCS))
-TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h,	\
-				 $$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\
-					       $$(TRUNNER_BPF_SRCS)))
-TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS) $$(LSKELS_EXTRA))
-TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS))
-TRUNNER_BPF_LSKELS_SIGNED := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS_SIGNED))
-TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS)
-
-# Evaluate rules now with extra TRUNNER_XXX variables above already defined
-$$(eval $$(call DEFINE_TEST_RUNNER_RULES,$1,$2))
-
-endef
-
-# Using TRUNNER_XXX variables, provided by callers of DEFINE_TEST_RUNNER and
-# set up by DEFINE_TEST_RUNNER itself, create test runner build rules with:
-# $1 - test runner base binary name (e.g., test_progs)
-# $2 - test runner extra "flavor" (e.g., no_alu32, cpuv4, bpf_gcc, etc)
-define DEFINE_TEST_RUNNER_RULES
-
-# Permissive build behaviour (skip-on-failure compile, partial-link) only
-# applies to test_progs and its flavors; runners that use strong cross-object
-# references (e.g. test_maps) keep strict semantics even when permissive.
-# The check is inlined per-runner so $1 is substituted at $(call) time and
-# the result is baked into each rule's recipe.
-
-ifeq ($($(TRUNNER_OUTPUT)-dir),)
-$(TRUNNER_OUTPUT)-dir := y
-$(TRUNNER_OUTPUT):
-	$$(call msg,MKDIR,,$$@)
-	$(Q)mkdir -p $$@
-endif
-
-# ensure we set up BPF objects generation rule just once for a given
-# input/output directory combination
-ifeq ($($(TRUNNER_BPF_PROGS_DIR)$(if $2,-)$2-bpfobjs),)
-$(TRUNNER_BPF_PROGS_DIR)$(if $2,-)$2-bpfobjs := y
-$(TRUNNER_BPF_OBJS): $(TRUNNER_OUTPUT)/%.bpf.o:				\
-		     $(TRUNNER_BPF_PROGS_DIR)/%.c			\
-		     $(TRUNNER_BPF_PROGS_DIR)/*.h			\
-		     $$(INCLUDE_DIR)/vmlinux.h				\
-		     $(HEADERS_FOR_BPF_OBJS)				\
-		     | $(TRUNNER_OUTPUT) $$(BPFOBJ)
-	$$(call $(TRUNNER_BPF_BUILD_RULE),$$<,$$@,			\
-					  $(TRUNNER_BPF_CFLAGS)         \
-					  $$($$<-CFLAGS)		\
-					  $$($$<-$2-CFLAGS),$(TRUNNER_BINARY))
-
-$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
-	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
-	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
-		--name $$(notdir $$(<:.bpf.o=))					\
-		--skel $$@ --subskel $$(@:.skel.h=.subskel.h) $$< $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))
-
-$(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
-	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
-	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
-		--name $$(notdir $$(<:.bpf.o=_lskel)) --lskel			\
-		--skel $$@ $$< $$(call skip_on_fail,SKEL)
-
-$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
-	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY) (signed),$$@)
-	$(Q)BPFTOOL=$$(BPFTOOL) PRIVATE_KEY=$(PRIVATE_KEY)			\
-		VERIFICATION_CERT=$(VERIFICATION_CERT)				\
-		./gen_bpf_skel.sh --sign					\
-		--name $$(notdir $$(<:.bpf.o=_lskel))				\
-		--skel $$@ $$< $$(call skip_on_fail,SKEL)
-
-$(LINKED_BPF_OBJS): %: $(TRUNNER_OUTPUT)/%
-
-# .SECONDEXPANSION here allows to correctly expand %-deps variables as prerequisites
-.SECONDEXPANSION:
-$(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_OUTPUT)/%: $$$$(%-deps) $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
-	$$(call msg,LINK-BPF,$(TRUNNER_BINARY),$$(@:.skel.h=.bpf.o))
-	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
-	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
-		--name $$(notdir $$(@:.skel.h=))				\
-		--skel $$@ --subskel $$(@:.skel.h=.subskel.h)			\
-		$$(addprefix $(TRUNNER_OUTPUT)/,$$($$(@F)-deps)) $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))
-
-# When the compiler generates a %.d file, only skel basenames (not
-# full paths) are specified as prerequisites for corresponding %.o
-# file. vpath directives below instruct make to search for skel files
-# in TRUNNER_OUTPUT, if they are not present in the working directory.
-vpath %.skel.h $(TRUNNER_OUTPUT)
-vpath %.lskel.h $(TRUNNER_OUTPUT)
-vpath %.subskel.h $(TRUNNER_OUTPUT)
-
-endif
-
-# ensure we set up tests.h header generation rule just once
-ifeq ($($(TRUNNER_TESTS_DIR)-tests-hdr),)
-$(TRUNNER_TESTS_DIR)-tests-hdr := y
-$(TRUNNER_TESTS_HDR): $(TRUNNER_TESTS_DIR)/*.c
-	$$(call msg,TEST-HDR,$(TRUNNER_BINARY),$$@)
-	$$(shell (echo '/* Generated header, do not edit */';					\
-		  sed -n -E 's/^void (serial_)?test_([a-zA-Z0-9_]+)\((void)?\).*/DEFINE_TEST(\2)/p'	\
-			$(TRUNNER_TESTS_DIR)/*.c | sort ;	\
-		 ) > $$@)
-endif
-
-$(TRUNNER_OUTPUT)/resolve_btfids.test.o: $(RESOLVE_BTFIDS) $(TRUNNER_OUTPUT)/btf_data.bpf.o
-$(TRUNNER_OUTPUT)/resolve_btfids.test.o: private TEST_NEEDS_BTFIDS = 1
-
-# compile individual test files
-# Note: we cd into output directory to ensure embedded BPF object is found
-$(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)/%.test.o:			\
-		      $(TRUNNER_TESTS_DIR)/%.c				\
-		      | $(TRUNNER_OUTPUT)/%.test.d
-	$$(call msg,TEST-OBJ,$(TRUNNER_BINARY),$$@)
-	$(Q)(cd $$(@D) && $$(CC) -I. $$(CFLAGS) -MMD -MT $$@ -c $(CURDIR)/$$< $$(LDLIBS) -o $$(@F)) $(if $(filter test_progs%,$1),$$(call skip_on_fail,TEST))
-	$$(if $$(TEST_NEEDS_BTFIDS),						\
-		$(Q)if [ -f $$@ ]; then						\
-		$(if $(filter 1,$(V)),true,printf '  %-8s%s %s\n' "BTFIDS" " [$(TRUNNER_BINARY)]" "$$(notdir $$@)"); \
-		$(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@;	\
-		$(RESOLVE_BTFIDS) --patch_btfids $$@.BTF_ids $$@;		\
-		fi)
-
-$(TRUNNER_TEST_OBJS:.o=.d): $(TRUNNER_OUTPUT)/%.test.d:			\
-			    $(TRUNNER_TESTS_DIR)/%.c			\
-			    $(TRUNNER_EXTRA_HDRS)			\
-			    $$(BPFOBJ) | $(TRUNNER_OUTPUT)		\
-			    $(TRUNNER_BPF_SKELS)			\
-			    $(TRUNNER_BPF_LSKELS)			\
-			    $(TRUNNER_BPF_LSKELS_SIGNED)		\
-			    $(TRUNNER_BPF_SKELS_LINKED)
-
-ifeq ($(filter clean docs-clean emit_tests,$(MAKECMDGOALS)),)
-include $(wildcard $(TRUNNER_TEST_OBJS:.o=.d))
-endif
-
-# add per extra obj CFGLAGS definitions
-$(foreach N,$(patsubst $(TRUNNER_OUTPUT)/%.o,%,$(TRUNNER_EXTRA_OBJS)),	\
-	$(eval $(TRUNNER_OUTPUT)/$(N).o: CFLAGS += $($(N).c-CFLAGS)))
-
-$(TRUNNER_EXTRA_OBJS): $(TRUNNER_OUTPUT)/%.o:				\
-		       %.c						\
-		       $(TRUNNER_EXTRA_HDRS)				\
-		       $(VERIFY_SIG_HDR)				\
-		       $(TRUNNER_TESTS_HDR)				\
-		       $$(BPFOBJ) | $(TRUNNER_OUTPUT)
-	$$(call msg,EXT-OBJ,$(TRUNNER_BINARY),$$@)
-	$(Q)$$(CC) $$(CFLAGS) -c $$< $$(LDLIBS) -o $$@
-
-$(TRUNNER_LIB_OBJS): $(TRUNNER_OUTPUT)/%.o:$(TOOLSDIR)/lib/%.c
-	$$(call msg,LIB-OBJ,$(TRUNNER_BINARY),$$@)
-	$(Q)$$(CC) $$(CFLAGS) -c $$< $$(LDLIBS) -o $$@
-
-# non-flavored in-srctree builds receive special treatment, in particular, we
-# do not need to copy extra resources (see e.g. test_btf_dump_case())
-$(TRUNNER_BINARY)-extras: $(TRUNNER_EXTRA_FILES) | $(TRUNNER_OUTPUT)
-ifneq ($2:$(OUTPUT),:$(shell pwd))
-	$$(call msg,EXT-COPY,$(TRUNNER_BINARY),$(TRUNNER_EXTRA_FILES))
-	$(Q)rsync -aq $(if $(PERMISSIVE),--ignore-missing-args) $$^ $(TRUNNER_OUTPUT)/
-endif
-
-# some X.test.o files have runtime dependencies on Y.bpf.o files
-$(OUTPUT)/$(TRUNNER_BINARY): | $(TRUNNER_BPF_OBJS)
-
-$(OUTPUT)/$(TRUNNER_BINARY): $(if $(filter test_progs%,$1),$(if $(PERMISSIVE),$$(wildcard $(TRUNNER_TEST_OBJS)),$(TRUNNER_TEST_OBJS)),$(TRUNNER_TEST_OBJS))	\
-			     $(TRUNNER_EXTRA_OBJS) $$(BPFOBJ)		\
-			     $(TRUNNER_LIB_OBJS)			\
-			     $(TRUNNER_BPFTOOL)				\
-			     $(OUTPUT)/veristat				\
-			     | $(TRUNNER_BINARY)-extras			\
-			     $(if $(filter test_progs%,$1),$(if $(PERMISSIVE),$(TRUNNER_TEST_OBJS)))
-	$$(call msg,BINARY,,$$@)
-	$(Q)$$(CC) $$(CFLAGS) $(if $(filter test_progs%,$1),$(if $(PERMISSIVE),$$(filter %.a %.o,$$(wildcard $(TRUNNER_TEST_OBJS)) $$(filter-out $(TRUNNER_TEST_OBJS),$$^)),$$(filter %.a %.o,$$^)),$$(filter %.a %.o,$$^)) $$(LDLIBS) $$(LLVM_LDLIBS) $$(LDFLAGS) $$(LLVM_LDFLAGS) -o $$@
-	$(Q)ln -sf $(if $2,..,.)/tools/build/bpftool/$(USE_BOOTSTRAP)bpftool \
-		   $(OUTPUT)/$(if $2,$2/)bpftool
-
-endef
-
-
 # One genkey run produces both files. A plain two-target rule is not
 # grouped - under -j make would run genkey twice concurrently and the
 # openssl invocations race; the pattern form is implicitly grouped even
@@ -672,81 +413,125 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
 	+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
 endif
 
-# Define test_progs test runner.
-TRUNNER_TESTS_DIR := prog_tests
-TRUNNER_BPF_PROGS_DIR := progs
-TRUNNER_EXTRA_SOURCES := test_progs.c		\
-			 cgroup_helpers.c	\
-			 trace_helpers.c	\
-			 network_helpers.c	\
-			 testing_helpers.c	\
-			 btf_helpers.c		\
-			 cap_helpers.c		\
-			 unpriv_helpers.c 	\
-			 sysctl_helpers.c	\
-			 netlink_helpers.c	\
-			 jit_disasm_helpers.c	\
-			 io_helpers.c		\
-			 test_loader.c		\
-			 xsk.c			\
-			 disasm.c		\
-			 disasm_helpers.c	\
-			 json_writer.c 		\
-			 $(VERIFY_SIG_HDR)		\
-			 flow_dissector_load.h	\
-			 ip_check_defrag_frags.h	\
-			 bpftool_helpers.c	\
-			 usdt_1.c usdt_2.c	\
-			 $(LIBARENA_SKEL)	\
-			 $(LIBARENA_ASAN_SKEL)
-TRUNNER_LIB_SOURCES := find_bit.c
-TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read				\
-		       $(OUTPUT)/liburandom_read.so			\
-		       $(OUTPUT)/xdp_synproxy				\
-		       $(OUTPUT)/sign-file				\
-		       $(OUTPUT)/uprobe_multi				\
-		       $(TEST_KMOD_TARGETS)				\
-		       ima_setup.sh 					\
-		       $(VERIFY_SIG_SETUP)				\
-		       $(wildcard progs/btf_dump_test_case_*.c)		\
-		       $(wildcard progs/*.bpf.o)
-TRUNNER_BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
-TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS) -DENABLE_ATOMICS_TESTS
-$(eval $(call DEFINE_TEST_RUNNER,test_progs))
-
-# Define test_progs-no_alu32 test runner.
-TRUNNER_BPF_BUILD_RULE := CLANG_NOALU32_BPF_BUILD_RULE
-TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS)
-$(eval $(call DEFINE_TEST_RUNNER,test_progs,no_alu32))
-
-# Define test_progs-cpuv4 test runner.
+# Generated test list headers
+
+define gen_tests_hdr
+	$(call msg,TEST-HDR,,$@)
+	$(Q)(echo '/* Generated header, do not edit */';		\
+		  sed -n -E 's/^void (serial_)?test_([a-zA-Z0-9_]+)\((void)?\).*/DEFINE_TEST(\2)/p'	\
+		$(@D)/*.c | sort) > $@
+endef
+
+prog_tests/tests.h: $(wildcard prog_tests/*.c)
+	$(gen_tests_hdr)
+
+map_tests/tests.h: $(wildcard map_tests/*.c)
+	$(gen_tests_hdr)
+
+# Test runner instances, one sub-make each (see Makefile.runner).
+
+# The LLVM feature-probe results, BPF_GCC and TEST_KMODS are exported
+# to the runner sub-makes. CC is passed explicitly instead: exporting it
+# would also leak lib.mk's CC into the libbpf/bpftool/resolve_btfids
+# sub-builds, which compute their own. ('export NAME' on an undefined
+# variable creates an empty one, so these stay below the definitions.)
+export LLVM_CFLAGS LLVM_LDLIBS LLVM_LDFLAGS BPF_GCC TEST_KMODS
+
+RUNNER_MAKE := $(MAKE) -f Makefile.runner OUTPUT=$(OUTPUT) CC='$(CC)'
+
+# Everything a runner instance references but does not know how to build.
+RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL)		\
+		  $(TRUNNER_BPFTOOL) $(RESOLVE_BTFIDS)			\
+		  $(OUTPUT)/veristat					\
+		  $(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT)	\
+		  $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)		\
+		  prog_tests/tests.h map_tests/tests.h			\
+		  $(HELPER_OBJS)					\
+		  $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
+		  $(OUTPUT)/xdp_synproxy $(OUTPUT)/sign-file		\
+		  $(OUTPUT)/uprobe_multi $(TEST_KMOD_TARGETS)
+
+FORCE:
+
+# The default flavor's skeletons are also inputs of bench, test_maps,
+# xskxceiver, xdp_* and test_cpp; "default-skels" builds just those
+# without the test_progs test objects and binary.
+.PHONY: default-skels
+default-skels: $(RUNNER_PREREQS)
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR= TESTS_DIR=prog_tests \
+		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
+		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v3'		\
+		BPF_DEFINES=-DENABLE_ATOMICS_TESTS skels
+
+$(OUTPUT)/test_progs: $(RUNNER_PREREQS) default-skels FORCE
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR= TESTS_DIR=prog_tests \
+		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
+		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v3'		\
+		BPF_DEFINES=-DENABLE_ATOMICS_TESTS
+
+$(OUTPUT)/test_progs-no_alu32: $(RUNNER_PREREQS) FORCE
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=no_alu32 TESTS_DIR=prog_tests \
+		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
+		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v2'
+
 ifneq ($(CLANG_CPUV4),)
-TRUNNER_BPF_BUILD_RULE := CLANG_CPUV4_BPF_BUILD_RULE
-TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS) -DENABLE_ATOMICS_TESTS
-$(eval $(call DEFINE_TEST_RUNNER,test_progs,cpuv4))
+$(OUTPUT)/test_progs-cpuv4: $(RUNNER_PREREQS) FORCE
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=cpuv4 TESTS_DIR=prog_tests \
+		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
+		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v4'		\
+		BPF_DEFINES=-DENABLE_ATOMICS_TESTS
 endif
 
-# Define test_progs BPF-GCC-flavored test runner.
 ifneq ($(BPF_GCC),)
-TRUNNER_BPF_BUILD_RULE := GCC_BPF_BUILD_RULE
-TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(call get_sys_includes,gcc,)
-$(eval $(call DEFINE_TEST_RUNNER,test_progs,bpf_gcc))
+# Sys includes come from the *host* gcc, not $(BPF_GCC) - bpf-gcc's own
+# search path lacks the host's asm-generic/ headers.
+GCC_SYS_INCLUDES := $(call get_sys_includes,gcc,)
+
+$(OUTPUT)/test_progs-bpf_gcc: $(RUNNER_PREREQS) FORCE
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=bpf_gcc TESTS_DIR=prog_tests \
+		BPF_CC='$(BPF_GCC)' BPF_CC_MSG=GCC-BPF				\
+		BPF_SYS_INCLUDES='$(GCC_SYS_INCLUDES)'				\
+		BPF_CC_FLAGS='-DBPF_NO_PRESERVE_ACCESS_INDEX -Wno-attributes -O2'
 endif
 
-# Define test_maps test runner.
-TRUNNER_TESTS_DIR := map_tests
-TRUNNER_BPF_PROGS_DIR := progs
-TRUNNER_EXTRA_SOURCES := test_maps.c
-TRUNNER_LIB_SOURCES :=
-TRUNNER_EXTRA_FILES :=
-TRUNNER_BPF_BUILD_RULE := $$(error no BPF objects should be built)
-TRUNNER_BPF_CFLAGS :=
-$(eval $(call DEFINE_TEST_RUNNER,test_maps))
-
-# Define test_verifier test runner.
-# It is much simpler than test_maps/test_progs and sufficiently different from
-# them (e.g., test.h is using completely pattern), that it's worth just
-# explicitly defining all the rules explicitly.
+# test_maps compiles map_tests/*.c against the default flavor's skeletons
+$(OUTPUT)/test_maps: $(RUNNER_PREREQS) map_tests/tests.h default-skels FORCE
+	+$(Q)$(RUNNER_MAKE) RUNNER=test_maps FLAVOR= TESTS_DIR=map_tests
+
+# Default-flavor BPF objects and skeletons consumed by targets in this
+# Makefile (and by lib.mk's install rule) are produced by the runner
+# sub-make above; the empty recipe plus the order-only prerequisite
+# makes them available here without duplicating the runner's rules.
+DEFAULT_BPF_OBJS := $(patsubst progs/%.c,$(OUTPUT)/%.bpf.o,$(wildcard progs/*.c))
+TEST_GEN_FILES += $(DEFAULT_BPF_OBJS)
+
+BENCH_SKELS := $(addprefix $(OUTPUT)/,					\
+	       test_overhead.skel.h trigger_bench.skel.h		\
+	       ringbuf_bench.skel.h perfbuf_bench.skel.h		\
+	       bloom_filter_bench.skel.h bpf_loop_bench.skel.h		\
+	       strncmp_bench.skel.h bpf_hashmap_full_update_bench.skel.h\
+	       local_storage_bench.skel.h				\
+	       local_storage_rcu_tasks_trace_bench.skel.h		\
+	       bench_local_storage_create.skel.h			\
+	       bpf_hashmap_lookup.skel.h htab_mem_bench.skel.h		\
+	       crypto_bench.skel.h bench_sockmap_prog.skel.h		\
+	       lpm_trie_bench.skel.h lpm_trie_map.skel.h		\
+	       bpf_nop_bench.skel.h xdp_lb_bench.skel.h)
+TOP_SKELS := $(addprefix $(OUTPUT)/,xsk_xdp_progs.skel.h		\
+	     xdp_hw_metadata.skel.h xdp_features.skel.h			\
+	     test_core_extern.skel.h) $(BENCH_SKELS)
+
+# Empty-recipe delegation: "default-skels" (re)builds the file via the
+# runner sub-make, after which make re-stats the target, so dependents
+# see fresh timestamps within the same invocation. Dependents still
+# rebuild on mtime only - the phony prerequisite does not cascade.
+$(DEFAULT_BPF_OBJS) $(TOP_SKELS): default-skels ;
+
+# Standalone test binaries
+
+# test_verifier is much simpler than test_maps/test_progs and
+# sufficiently different from them (e.g., its tests.h uses a completely
+# different pattern), that it's worth just defining its rules explicitly.
 verifier/tests.h: verifier/*.c
 	$(Q)( cd verifier/; \
 		  echo '/* Generated header, do not edit */'; \
@@ -754,6 +539,7 @@ verifier/tests.h: verifier/*.c
 		  ls *.c 2> /dev/null | sed -e 's@\(.*\)@#include \"\1\"@'; \
 		  echo '#endif' \
 		) > verifier/tests.h
+
 $(OUTPUT)/test_verifier: test_verifier.c verifier/tests.h $(BPFOBJ) | $(OUTPUT)
 	$(call msg,BINARY,,$@)
 	$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
@@ -780,8 +566,9 @@ $(OUTPUT)/test_cpp: test_cpp.cpp $(OUTPUT)/test_core_extern.skel.h $(BPFOBJ)
 	$(call msg,CXX,,$@)
 	$(Q)$(CXX) $(CXXFLAGS) $(filter %.a %.o %.cpp,$^) $(LDLIBS) -o $@
 
-# Benchmark runner
-$(OUTPUT)/bench_%.o: benchs/bench_%.c bench.h $(BPFOBJ)
+# Benchmark runner. Every benchs/bench_*.c is compiled and linked in.
+BENCH_OBJS := $(sort $(patsubst benchs/%.c,$(OUTPUT)/%.o,$(wildcard benchs/bench_*.c)))
+$(OUTPUT)/bench_%.o: benchs/bench_%.c bench.h $(BPFOBJ) | default-skels
 	$(call msg,CC,,$@)
 	$(Q)$(CC) $(CFLAGS) -O2 -c $(filter %.c,$^) $(LDLIBS) -o $@ $(call skip_on_fail,BENCH)
 $(OUTPUT)/bench_rename.o: $(OUTPUT)/test_overhead.skel.h
@@ -809,28 +596,9 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
 		 $(TESTING_HELPERS) \
 		 $(TRACE_HELPERS) \
 		 $(CGROUP_HELPERS) \
-		 $(OUTPUT)/bench_count.o \
-		 $(OUTPUT)/bench_rename.o \
-		 $(OUTPUT)/bench_trigger.o \
-		 $(OUTPUT)/bench_ringbufs.o \
-		 $(OUTPUT)/bench_bloom_filter_map.o \
-		 $(OUTPUT)/bench_bpf_loop.o \
-		 $(OUTPUT)/bench_strncmp.o \
-		 $(OUTPUT)/bench_bpf_hashmap_full_update.o \
-		 $(OUTPUT)/bench_local_storage.o \
-		 $(OUTPUT)/bench_local_storage_rcu_tasks_trace.o \
-		 $(OUTPUT)/bench_bpf_hashmap_lookup.o \
-		 $(OUTPUT)/bench_local_storage_create.o \
-		 $(OUTPUT)/bench_htab_mem.o \
-		 $(OUTPUT)/bench_bpf_crypto.o \
-		 $(OUTPUT)/bench_sockmap.o \
-		 $(OUTPUT)/bench_lpm_trie_map.o \
-		 $(OUTPUT)/bench_bpf_timing.o \
-		 $(OUTPUT)/bench_bpf_nop.o \
-		 $(OUTPUT)/bench_xdp_lb.o \
+		 $(BENCH_OBJS) \
 		 $(OUTPUT)/usdt_1.o \
-		 $(OUTPUT)/usdt_2.o \
-		 #
+		 $(OUTPUT)/usdt_2.o
 	$(call msg,BINARY,,$@)
 	$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(call skip_on_fail,LINK,,(some benchmarks may have been skipped))
 
diff --git a/tools/testing/selftests/bpf/Makefile.runner b/tools/testing/selftests/bpf/Makefile.runner
new file mode 100644
index 000000000..91e8a2df4
--- /dev/null
+++ b/tools/testing/selftests/bpf/Makefile.runner
@@ -0,0 +1,320 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Build one BPF test-runner instance: test_progs, one of its flavors
+# (no_alu32, cpuv4, bpf_gcc), or test_maps.
+#
+# Each instance is an independent sub-make with its own output directory,
+# so everything here is written in plain make - no $$-escaping, no eval,
+# no per-flavor guards. This file is always invoked by the main Makefile,
+# never directly: all shared prerequisites (libbpf, bpftool, vmlinux.h,
+# veristat, helper objects, signing key, generated tests.h, extra
+# binaries) are built by the main Makefile *before* this one runs and are
+# referenced below as plain files.
+#
+# Parameters (all passed on the sub-make command line):
+#   OUTPUT      absolute path of the selftests output directory
+#   RUNNER      base binary name: test_progs | test_maps
+#   FLAVOR      flavor suffix: empty | no_alu32 | cpuv4 | bpf_gcc
+#   TESTS_DIR   directory with the test sources: prog_tests | map_tests
+#   BPF_CC      compiler for progs/*.c BPF objects ($(CLANG) or
+#               $(BPF_GCC)); empty for test_maps, which builds none
+#   BPF_CC_MSG  build-log tag: CLNG-BPF | GCC-BPF
+#   BPF_CC_FLAGS
+#               compiler-specific flags (-O2, target/-mcpu or gcc knobs)
+#   BPF_SYS_INCLUDES
+#               system include flags matching BPF_CC
+#   BPF_DEFINES extra defines for BPF objects (e.g. -DENABLE_ATOMICS_TESTS)
+#   CC          C compiler (as resolved by lib.mk in the main Makefile)
+#   LLVM_CFLAGS/LLVM_LDLIBS/LLVM_LDFLAGS
+#               results of the top-level LLVM feature probe
+
+include ../../../build/Build.include
+include ../../../scripts/Makefile.arch
+include ../../../scripts/Makefile.include
+
+# Same message helpers as ../lib.mk, which only the top level includes.
+ifeq ($(V),1)
+Q =
+msg =
+else
+Q = @
+msg = @printf '  %-8s%s %s%s\n' "$(1)" "$(if $(2), [$(2)])" "$(notdir $(3))" "$(if $(4), $(4))";
+MAKEFLAGS += --no-print-directory
+endif
+
+include Makefile.buildvars
+
+# Keep in sync with the CFLAGS/LDFLAGS additions in ../lib.mk.
+CFLAGS += -D_GNU_SOURCE=
+CFLAGS += -I$(srctree)/tools/testing/selftests
+CFLAGS += $(USERCFLAGS)
+LDFLAGS += $(USERLDFLAGS)
+ifneq ($(LLVM),)
+CFLAGS += -Wno-address-of-packed-member
+CFLAGS += -Wno-gnu-variable-sized-type-not-at-end
+endif
+
+BINARY := $(RUNNER)$(if $(FLAVOR),-$(FLAVOR))
+RDIR   := $(OUTPUT)$(if $(FLAVOR),/$(FLAVOR))
+
+all: $(OUTPUT)/$(BINARY)
+.PHONY: all
+
+# Delete partially updated (corrupted) files on error
+.DELETE_ON_ERROR:
+
+# Permissive skip-on-failure of individual tests applies to test_progs and
+# its flavors only; test_maps uses strong cross-object references and keeps
+# strict semantics even when BPF_STRICT_BUILD=0.
+PERMISSIVE_TESTS := $(if $(filter test_progs,$(RUNNER)),$(PERMISSIVE))
+
+$(RDIR):
+	$(call msg,MKDIR,,$@)
+	$(Q)mkdir -p $@
+
+# ---------------------------------------------------------------------
+# Per-runner sources
+# ---------------------------------------------------------------------
+
+ifeq ($(RUNNER),test_progs)
+EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c		\
+		 network_helpers.c testing_helpers.c btf_helpers.c	\
+		 cap_helpers.c unpriv_helpers.c sysctl_helpers.c	\
+		 netlink_helpers.c jit_disasm_helpers.c io_helpers.c	\
+		 test_loader.c xsk.c disasm.c disasm_helpers.c		\
+		 json_writer.c bpftool_helpers.c usdt_1.c usdt_2.c
+LIB_SOURCES := find_bit.c
+EXTRA_HDRS := flow_dissector_load.h ip_check_defrag_frags.h		\
+	      $(VERIFY_SIG_HDR) $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)
+EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
+	       $(OUTPUT)/xdp_synproxy $(OUTPUT)/sign-file		\
+	       $(OUTPUT)/uprobe_multi					\
+	       $(addprefix $(OUTPUT)/,$(TEST_KMODS))			\
+	       ima_setup.sh $(VERIFY_SIG_SETUP)				\
+	       $(wildcard progs/btf_dump_test_case_*.c)			\
+	       $(wildcard progs/*.bpf.o)
+endif
+
+ifeq ($(RUNNER),test_maps)
+EXTRA_SOURCES := test_maps.c testing_helpers.c
+endif
+
+# Note on the unflavored runners, which share $(OUTPUT) with the main
+# Makefile: helper objects the main Makefile also uses (and lists in the
+# runner prerequisites) are already built and fresh when a runner starts,
+# so the rules below find them up to date and never write them. This
+# holds because the main Makefile's helper-object rule depends on a
+# superset of the prerequisites the EXTRA_OBJS rule uses here - keep
+# the two lists in sync (see the invariant comment there).
+
+TESTS_HDR := $(TESTS_DIR)/tests.h
+
+TEST_SRCS  := $(notdir $(wildcard $(TESTS_DIR)/*.c))
+TEST_OBJS  := $(patsubst %.c,$(RDIR)/%.test.o,$(TEST_SRCS))
+TEST_DEPS  := $(TEST_OBJS:.o=.d)
+EXTRA_OBJS := $(patsubst %.c,$(RDIR)/%.o,$(filter %.c,$(EXTRA_SOURCES)))
+LIB_OBJS   := $(patsubst %.c,$(RDIR)/%.o,$(LIB_SOURCES))
+
+# ---------------------------------------------------------------------
+# BPF objects and skeletons
+# ---------------------------------------------------------------------
+
+ifneq ($(BPF_CC),)
+
+BPF_SRCS := $(notdir $(wildcard progs/*.c))
+BPF_OBJS := $(patsubst %.c,$(RDIR)/%.bpf.o,$(BPF_SRCS))
+
+# BPF objects that cannot have a regular skeleton.
+SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
+
+LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c 	\
+	core_kern.c core_kern_overflow.c test_ringbuf.c			\
+	test_ringbuf_n.c test_ringbuf_map_key.c test_ringbuf_write.c    \
+	test_ringbuf_overwrite.c
+
+LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c
+
+# Generate both light skeleton and libbpf skeleton for these
+LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \
+	kfunc_call_test_subprog.c
+SKEL_BLACKLIST += $(LSKELS) $(LSKELS_SIGNED)
+
+# Skeletons made by linking multiple BPF objects together. The %-deps
+# mapping is the only hand-maintained metadata: it cannot be derived from
+# the sources.
+test_static_linked.skel.h-deps := test_static_linked1.bpf.o test_static_linked2.bpf.o
+linked_funcs.skel.h-deps := linked_funcs1.bpf.o linked_funcs2.bpf.o
+linked_vars.skel.h-deps := linked_vars1.bpf.o linked_vars2.bpf.o
+linked_maps.skel.h-deps := linked_maps1.bpf.o linked_maps2.bpf.o
+# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
+# but that's created as a side-effect of the skel.h generation.
+test_subskeleton.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o test_subskeleton.bpf.o
+test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o
+test_usdt.skel.h-deps := test_usdt.bpf.o test_usdt_multispec.bpf.o
+tracing_multi.skel.h-deps := tracing_multi_attach.bpf.o tracing_multi_check.bpf.o
+tracing_multi_module.skel.h-deps := tracing_multi_attach_module.bpf.o tracing_multi_check.bpf.o
+tracing_multi_intersect.skel.h-deps := tracing_multi_intersect_attach.bpf.o tracing_multi_check.bpf.o
+tracing_multi_session.skel.h-deps := tracing_multi_session_attach.bpf.o tracing_multi_check.bpf.o
+
+LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h		\
+		linked_vars.skel.h linked_maps.skel.h			\
+		test_subskeleton.skel.h test_subskeleton_lib.skel.h	\
+		test_usdt.skel.h tracing_multi.skel.h			\
+		tracing_multi_module.skel.h				\
+		tracing_multi_intersect.skel.h				\
+		tracing_multi_session.skel.h
+LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps)))
+
+SKELS := $(patsubst %.c,$(RDIR)/%.skel.h,				\
+	   $(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),$(BPF_SRCS)))
+LSKELS_H        := $(patsubst %.c,$(RDIR)/%.lskel.h,$(LSKELS) $(LSKELS_EXTRA))
+LSKELS_SIGNED_H := $(patsubst %.c,$(RDIR)/%.lskel.h,$(LSKELS_SIGNED))
+LINKED_SKELS_H  := $(addprefix $(RDIR)/,$(LINKED_SKELS))
+ALL_SKELS := $(SKELS) $(LSKELS_H) $(LSKELS_SIGNED_H) $(LINKED_SKELS_H)
+
+HEADERS_FOR_BPF_OBJS := $(wildcard $(BPFDIR)/*.bpf.h)			\
+			$(wildcard $(CURDIR)/libarena/include/*.[ch])	\
+			$(addprefix $(BPFDIR)/,	bpf_core_read.h		\
+						bpf_endian.h		\
+						bpf_helpers.h		\
+						bpf_tracing.h)
+
+# Per-source BPF compilation flag overrides ($<-CFLAGS / $<-$(FLAVOR)-CFLAGS).
+# GCC cannot disable "declaration of anonymous struct in parameter list"
+# warnings triggered by these (technically legal) test programs.
+progs/btf_dump_test_case_bitfields.c-bpf_gcc-CFLAGS := -Wno-error
+progs/btf_dump_test_case_namespacing.c-bpf_gcc-CFLAGS := -Wno-error
+progs/btf_dump_test_case_packing.c-bpf_gcc-CFLAGS := -Wno-error
+progs/btf_dump_test_case_padding.c-bpf_gcc-CFLAGS := -Wno-error
+progs/btf_dump_test_case_syntax.c-bpf_gcc-CFLAGS := -Wno-error
+
+$(BPF_OBJS): $(RDIR)/%.bpf.o: progs/%.c progs/*.h			\
+	     $(INCLUDE_DIR)/vmlinux.h $(HEADERS_FOR_BPF_OBJS)		\
+	     | $(RDIR) $(BPFOBJ)
+	$(call msg,$(BPF_CC_MSG),$(BINARY),$@)
+	$(Q)$(BPF_CC) $(BPF_CFLAGS) $(BPF_SYS_INCLUDES) $(BPF_DEFINES)	\
+		$($<-CFLAGS) $($<-$(FLAVOR)-CFLAGS)			\
+		$(BPF_CC_FLAGS) -c $< -o $@ $(call skip_on_fail,BPF)
+
+# progs/cgroup_getset_retval_hooks.c includes this top-directory header,
+# which the progs/*.h prerequisite above does not cover.
+$(RDIR)/cgroup_getset_retval_hooks.bpf.o: cgroup_getset_retval_hooks.h
+
+# gen_bpf_skel.sh picks the bpftool binary up from the environment.
+export BPFTOOL
+GEN_SKEL := ./gen_bpf_skel.sh
+
+$(SKELS): $(RDIR)/%.skel.h: $(RDIR)/%.bpf.o $(BPFTOOL) $(GEN_SKEL) | $(RDIR)
+	$(call msg,GEN-SKEL,$(BINARY),$@)
+	$(Q)$(GEN_SKEL) --name $*					\
+		--skel $@ --subskel $(@:.skel.h=.subskel.h) $<		\
+		$(call skip_on_fail,SKEL,$(@:.skel.h=.subskel.h))
+
+$(LSKELS_H): $(RDIR)/%.lskel.h: $(RDIR)/%.bpf.o $(BPFTOOL) $(GEN_SKEL) | $(RDIR)
+	$(call msg,GEN-SKEL,$(BINARY),$@)
+	$(Q)$(GEN_SKEL) --name $*_lskel --lskel				\
+		--skel $@ $< $(call skip_on_fail,SKEL)
+
+$(LSKELS_SIGNED_H): $(RDIR)/%.lskel.h: $(RDIR)/%.bpf.o $(BPFTOOL)	\
+		    $(GEN_SKEL) $(PRIVATE_KEY) $(VERIFICATION_CERT) | $(RDIR)
+	$(call msg,GEN-SKEL,$(BINARY) (signed),$@)
+	$(Q)PRIVATE_KEY=$(PRIVATE_KEY)					\
+		VERIFICATION_CERT=$(VERIFICATION_CERT)			\
+		$(GEN_SKEL) --sign --name $*_lskel			\
+		--skel $@ $< $(call skip_on_fail,SKEL)
+
+.SECONDEXPANSION:
+$(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps)) \
+		   $(BPFTOOL) $(GEN_SKEL) | $(RDIR)
+	$(call msg,LINK-BPF,$(BINARY),$(@:.skel.h=.bpf.o))
+	$(call msg,GEN-SKEL,$(BINARY),$@)
+	$(Q)$(GEN_SKEL) --name $*					\
+		--skel $@ --subskel $(@:.skel.h=.subskel.h)		\
+		$(addprefix $(RDIR)/,$($*.skel.h-deps))			\
+		$(call skip_on_fail,SKEL,$(@:.skel.h=.subskel.h))
+
+endif # BPF_CC
+
+.PHONY: skels
+skels: $(ALL_SKELS)
+
+# When the compiler generates a %.d file, only skel basenames (not full
+# paths) are specified as prerequisites for the corresponding %.o file.
+# The vpath directives instruct make to also search for skel files in
+# $(RDIR) if they are not present in the working directory.
+vpath %.skel.h $(RDIR)
+vpath %.lskel.h $(RDIR)
+vpath %.subskel.h $(RDIR)
+
+# ---------------------------------------------------------------------
+# Test objects and runner binary
+# ---------------------------------------------------------------------
+
+$(RDIR)/resolve_btfids.test.o: $(RESOLVE_BTFIDS) $(RDIR)/btf_data.bpf.o
+$(RDIR)/resolve_btfids.test.o: private TEST_NEEDS_BTFIDS = 1
+
+# Compile individual test files.
+# Note: we cd into the output directory to ensure embedded BPF objects
+# are found. The %.test.d dependency files are a side effect of the -MMD
+# below; the separate no-recipe rule for them orders the first
+# compilation after skeleton generation and re-orders test object
+# compilation when headers change.
+$(TEST_OBJS): $(RDIR)/%.test.o: $(TESTS_DIR)/%.c | $(RDIR)/%.test.d
+	$(call msg,TEST-OBJ,$(BINARY),$@)
+	$(Q)(cd $(@D) && $(CC) -I. $(CFLAGS) -MMD -MT $@ -c $(CURDIR)/$< -o $(@F)) \
+		$(if $(PERMISSIVE_TESTS),$(call skip_on_fail,TEST))
+	$(if $(TEST_NEEDS_BTFIDS),					\
+		$(Q)if [ -f $@ ]; then					\
+		$(if $(filter 1,$(V)),true,printf '  %-8s%s %s\n' "BTFIDS" " [$(BINARY)]" "$(notdir $@)"); \
+		$(RESOLVE_BTFIDS) --btf $(RDIR)/btf_data.bpf.o $@;	\
+		$(RESOLVE_BTFIDS) --patch_btfids $@.BTF_ids $@;		\
+		fi)
+
+$(TEST_DEPS): $(RDIR)/%.test.d: $(TESTS_DIR)/%.c $(EXTRA_HDRS) $(BPFOBJ) \
+	      | $(RDIR) $(ALL_SKELS)
+
+include $(wildcard $(TEST_DEPS))
+
+# Some utility functions use LLVM libraries
+$(RDIR)/jit_disasm_helpers.o: CFLAGS += $(LLVM_CFLAGS)
+
+$(EXTRA_OBJS): $(RDIR)/%.o: %.c $(EXTRA_HDRS) $(TESTS_HDR) $(BPFOBJ) | $(RDIR)
+	$(call msg,EXT-OBJ,$(BINARY),$@)
+	$(Q)$(CC) $(CFLAGS) -c $< -o $@
+
+$(LIB_OBJS): $(RDIR)/%.o: $(TOOLSDIR)/lib/%.c | $(RDIR)
+	$(call msg,LIB-OBJ,$(BINARY),$@)
+	$(Q)$(CC) $(CFLAGS) -c $< -o $@
+
+# Non-flavored in-srctree builds receive special treatment, in particular
+# we do not need to copy extra resources (see e.g. test_btf_dump_case()).
+.PHONY: extras
+extras: $(if $(PERMISSIVE),$(wildcard $(EXTRA_FILES)),$(EXTRA_FILES)) | $(RDIR)
+ifneq ($(FLAVOR):$(OUTPUT),:$(CURDIR))
+ifneq ($(strip $(EXTRA_FILES)),)
+	$(call msg,EXT-COPY,$(BINARY),$(notdir $(EXTRA_FILES)))
+	$(Q)rsync -aq $(if $(PERMISSIVE),--ignore-missing-args) $(EXTRA_FILES) $(RDIR)/
+endif
+endif
+
+# In permissive mode, link whatever test objects were successfully built
+# (their compilation may have been skipped): the objects existing at
+# parse time are normal prerequisites - so editing a test source still
+# relinks the runner - while the full set stays order-only to drive the
+# build attempts, and the wildcard in the recipe picks up the survivors
+# at link time.
+# Prerequisite order is also link order (test objects, extra objects,
+# libbpf, lib objects).
+$(OUTPUT)/$(BINARY): $(if $(PERMISSIVE_TESTS),$(wildcard $(TEST_OBJS)),$(TEST_OBJS)) \
+		     $(EXTRA_OBJS) $(BPFOBJ) $(LIB_OBJS)		\
+		     $(TRUNNER_BPFTOOL) $(OUTPUT)/veristat		\
+		     | extras $(BPF_OBJS)				\
+		     $(if $(PERMISSIVE_TESTS),$(TEST_OBJS))
+	$(call msg,BINARY,,$@)
+	$(Q)$(CC) $(CFLAGS)						\
+		$(if $(PERMISSIVE_TESTS),$(wildcard $(TEST_OBJS))	\
+			$(filter-out $(TEST_OBJS),$(filter %.a %.o,$^)),\
+			$(filter %.a %.o,$^))				\
+		$(LDLIBS) $(LLVM_LDLIBS) $(LDFLAGS) $(LLVM_LDFLAGS) -o $@
+	$(Q)ln -sf $(if $(FLAVOR),..,.)/tools/build/bpftool/$(USE_BOOTSTRAP)bpftool \
+		   $(RDIR)/bpftool
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper Mykola Lysenko
@ 2026-08-04 17:21   ` sashiko-bot
  2026-08-05  0:13     ` Mykola Lysenko
  2026-08-05 21:33   ` Eduard Zingerman
  1 sibling, 1 reply; 23+ messages in thread
From: sashiko-bot @ 2026-08-04 17:21 UTC (permalink / raw)
  To: Mykola Lysenko; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing space in SKIP message output due to GNU Make's function argument whitespace stripping.
--

--- Patch [3]: [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper ---
commit 5a41b62fa6a5d8d794a18de41a7ff9bb6b7087f6
Author: Mykola Lysenko <nickolay.lysenko@gmail.com>

selftests/bpf: factor the permissive-mode skip suffix into a helper

This commit factors out the duplicated skip logic used for permissive builds
into a common skip_on_fail macro in the Makefile.

> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -49,6 +49,14 @@ SKIP_CRYPTO	?=
>  BPF_STRICT_BUILD ?= 1
>  PERMISSIVE := $(filter 0,$(BPF_STRICT_BUILD))
>  
> +# Permissive-mode recipe suffix: on failure, remove the target, emit a
> +# SKIP marker and report success so the rest of the build continues.
> +# $(1) - SKIP tag (BPF, TEST, BENCH, SKEL, LINK, ...)
> +# $(2) - optional extra files to remove alongside the target
> +# $(3) - optional note appended to the SKIP message
> +skip_on_fail = $(if $(PERMISSIVE),|| { $(RM) $@ $(2); \
> +	printf '  %-12s %s\n' 'SKIP-$(1)' '$(notdir $@)$(if $(3), $(3))' 1>&2; })

[Severity: Low]
Will GNU Make strip the leading space before $(3) in this if statement?

Since Make strips leading whitespace after commas in function arguments, it
appears the optional note gets appended directly to the target name without
any separation space.

[ ... ]

> @@ -1012,8 +1002,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
>  		 $(OUTPUT)/usdt_2.o \
>  		 #
>  	$(call msg,BINARY,,$@)
> -	$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(if $(PERMISSIVE),|| \
> -		($(RM) $@; printf '  %-12s %s\n' 'SKIP-LINK' '$(notdir $@) (some benchmarks may have been skipped)' 1>&2))
> +	$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(call skip_on_fail,LINK,,(some benchmarks may have been skipped))

[Severity: Low]
Because of the stripped whitespace mentioned above, will the console output
for this rule now print as "bench(some benchmarks may have been skipped)"
instead of "bench (some benchmarks may have been skipped)"?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804170156.1709916-1-nickolay.lysenko@gmail.com?part=3

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script Mykola Lysenko
@ 2026-08-04 17:59   ` sashiko-bot
  2026-08-05  1:40     ` Mykola Lysenko
  2026-08-05 22:39   ` Eduard Zingerman
  1 sibling, 1 reply; 23+ messages in thread
From: sashiko-bot @ 2026-08-04 17:59 UTC (permalink / raw)
  To: Mykola Lysenko; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch introduces a behavior change in permissive mode that results in noisy build logs and redundant error messages, contradicting the commit message claim that 'behavior is unchanged'. Additionally, a script comment references a non-existent file.
--

--- Patch [6]: [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script ---
commit e4a593d3d0bfe3675d723364cb65f81a2812b866
Author: Mykola Lysenko <nickolay.lysenko@gmail.com>

selftests/bpf: extract BPF skeleton generation into a helper script

This commit extracts the BPF skeleton generation steps into a dedicated shell
script (gen_bpf_skel.sh) to reduce Makefile complexity and escaping issues. It
handles regular, light, signed light, and linked skeletons through script
flags.

> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 0ee02eeac6bca..dbde2f6bd044d 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile

[ ... ]

> -$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
> -	$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then			\
> -		$$(RM) $$@ $$(@:.skel.h=.subskel.h);			\
> -		printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
> -		exit 0;							\
> -	fi;)								\
> -	printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2; \
> -	$$(BPFTOOL) gen object $$(<:.o=.linked1.o) $$< &&		\

[ ... ]

> +$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
> +	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> +	$(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh				\
> +		--name $$(notdir $$(<:.bpf.o=))					\
> +		--skel $$@ --subskel $$(@:.skel.h=.subskel.h) $$< $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))

[Severity: Medium]
Does this change in permissive mode behavior contradict the commit message
which states "behavior is unchanged"?

Previously, if a .bpf.o file failed to compile in permissive mode, the
skeleton generation recipe would quietly check for the missing file, print
SKIP-SKEL, and exit early without executing bpftool.

With the missing-file guard removed, the recipe now unconditionally prints
GEN-SKEL and calls gen_bpf_skel.sh. This invokes bpftool on a missing file,
causing it to fail loudly to stderr before the skip_on_fail suffix catches
the exit code. This results in noisy build logs and redundant error messages.

> diff --git a/tools/testing/selftests/bpf/gen_bpf_skel.sh b/tools/testing/selftests/bpf/gen_bpf_skel.sh
> new file mode 100755
> index 0000000000000..e234f3a57d46b
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/gen_bpf_skel.sh
> @@ -0,0 +1,93 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0

[ ... ]

> +# The bpftool binary is taken from $BPFTOOL (default: bpftool from PATH).
> +# On failure all outputs and intermediates are removed and the script
> +# exits non-zero; permissive-mode skipping is the caller's business
> +# (see skip_on_fail in Makefile.buildvars).

[Severity: Medium]
Is the reference to Makefile.buildvars correct here?

The file Makefile.buildvars does not exist. It looks like skip_on_fail is
actually defined directly in tools/testing/selftests/bpf/Makefile.

> +
> +set -u

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804170156.1709916-1-nickolay.lysenko@gmail.com?part=6

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper
  2026-08-04 17:21   ` sashiko-bot
@ 2026-08-05  0:13     ` Mykola Lysenko
  0 siblings, 0 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-05  0:13 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf

On Tue, Aug 4, 2026 at 10:21 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Missing space in SKIP message output due to GNU Make's function argument whitespace stripping.
> --
>
> --- Patch [3]: [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper ---
> commit 5a41b62fa6a5d8d794a18de41a7ff9bb6b7087f6
> Author: Mykola Lysenko <nickolay.lysenko@gmail.com>
>
> selftests/bpf: factor the permissive-mode skip suffix into a helper
>
> This commit factors out the duplicated skip logic used for permissive builds
> into a common skip_on_fail macro in the Makefile.
>
> > diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> > --- a/tools/testing/selftests/bpf/Makefile
> > +++ b/tools/testing/selftests/bpf/Makefile
> > @@ -49,6 +49,14 @@ SKIP_CRYPTO        ?=
> >  BPF_STRICT_BUILD ?= 1
> >  PERMISSIVE := $(filter 0,$(BPF_STRICT_BUILD))
> >
> > +# Permissive-mode recipe suffix: on failure, remove the target, emit a
> > +# SKIP marker and report success so the rest of the build continues.
> > +# $(1) - SKIP tag (BPF, TEST, BENCH, SKEL, LINK, ...)
> > +# $(2) - optional extra files to remove alongside the target
> > +# $(3) - optional note appended to the SKIP message
> > +skip_on_fail = $(if $(PERMISSIVE),|| { $(RM) $@ $(2); \
> > +     printf '  %-12s %s\n' 'SKIP-$(1)' '$(notdir $@)$(if $(3), $(3))' 1>&2; })
>
> [Severity: Low]
> Will GNU Make strip the leading space before $(3) in this if statement?
>
> Since Make strips leading whitespace after commas in function arguments, it
> appears the optional note gets appended directly to the target name without
> any separation space.
>
> [ ... ]
>
> > @@ -1012,8 +1002,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
> >                $(OUTPUT)/usdt_2.o \
> >                #
> >       $(call msg,BINARY,,$@)
> > -     $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(if $(PERMISSIVE),|| \
> > -             ($(RM) $@; printf '  %-12s %s\n' 'SKIP-LINK' '$(notdir $@) (some benchmarks may have been skipped)' 1>&2))
> > +     $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@ $(call skip_on_fail,LINK,,(some benchmarks may have been skipped))
>
> [Severity: Low]
> Because of the stripped whitespace mentioned above, will the console output
> for this rule now print as "bench(some benchmarks may have been skipped)"
> instead of "bench (some benchmarks may have been skipped)"?

does not seems to be the case

prozak@powertower:/tmp/tmp.U6wCzgIl5v$ make --version
GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

prozak@powertower:/tmp/tmp.U6wCzgIl5v$ cat Makefile
PERMISSIVE := 1

skip_on_fail = $(if $(PERMISSIVE),||  \
{ printf "  %-12s %s\n" "SKIP-$(1)" "$(notdir $@)$(if $(3), $(3))" 1>&2; })

bench:
@false \
$(call skip_on_fail,LINK,,(some benchmarks may have been skipped))

prozak@powertower:/tmp/tmp.U6wCzgIl5v$ make bench
  SKIP-LINK    bench (some benchmarks may have been skipped)

>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260804170156.1709916-1-nickolay.lysenko@gmail.com?part=3

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script
  2026-08-04 17:59   ` sashiko-bot
@ 2026-08-05  1:40     ` Mykola Lysenko
  0 siblings, 0 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-05  1:40 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf

On Tue, Aug 4, 2026 at 10:59 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The patch introduces a behavior change in permissive mode that results in noisy build logs and redundant error messages, contradicting the commit message claim that 'behavior is unchanged'. Additionally, a script comment references a non-existent file.
> --
>
> --- Patch [6]: [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script ---
> commit e4a593d3d0bfe3675d723364cb65f81a2812b866
> Author: Mykola Lysenko <nickolay.lysenko@gmail.com>
>
> selftests/bpf: extract BPF skeleton generation into a helper script
>
> This commit extracts the BPF skeleton generation steps into a dedicated shell
> script (gen_bpf_skel.sh) to reduce Makefile complexity and escaping issues. It
> handles regular, light, signed light, and linked skeletons through script
> flags.
>
> > diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> > index 0ee02eeac6bca..dbde2f6bd044d 100644
> > --- a/tools/testing/selftests/bpf/Makefile
> > +++ b/tools/testing/selftests/bpf/Makefile
>
> [ ... ]
>
> > -$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
> > -     $(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then                    \
> > -             $$(RM) $$@ $$(@:.skel.h=.subskel.h);                    \
> > -             printf '  %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
> > -             exit 0;                                                 \
> > -     fi;)                                                            \
> > -     printf '  %-12s %s\n' 'GEN-SKEL' '[$(TRUNNER_BINARY)] $$(notdir $$@)' 1>&2; \
> > -     $$(BPFTOOL) gen object $$(<:.o=.linked1.o) $$< &&               \
>
> [ ... ]
>
> > +$(TRUNNER_BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) gen_bpf_skel.sh | $(TRUNNER_OUTPUT)
> > +     $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> > +     $(Q)BPFTOOL=$$(BPFTOOL) ./gen_bpf_skel.sh                               \
> > +             --name $$(notdir $$(<:.bpf.o=))                                 \
> > +             --skel $$@ --subskel $$(@:.skel.h=.subskel.h) $$< $$(call skip_on_fail,SKEL,$$(@:.skel.h=.subskel.h))
>
> [Severity: Medium]
> Does this change in permissive mode behavior contradict the commit message
> which states "behavior is unchanged"?
>
> Previously, if a .bpf.o file failed to compile in permissive mode, the
> skeleton generation recipe would quietly check for the missing file, print
> SKIP-SKEL, and exit early without executing bpftool.
>
> With the missing-file guard removed, the recipe now unconditionally prints
> GEN-SKEL and calls gen_bpf_skel.sh. This invokes bpftool on a missing file,
> causing it to fail loudly to stderr before the skip_on_fail suffix catches
> the exit code. This results in noisy build logs and redundant error messages.

It does - good catch. Reproduced:

prozak@powertower$ touch progs/fentry_test.c && $MK LLVM= BPF_STRICT_BUILD=0 \
 "progs/fentry_test.c-CFLAGS=--definitely-bogus-flag" \
 $PWD/fentry_test.bpf.o $PWD/fentry_test.lskel.h 2>&1 | tail -15
...
  CLNG-BPF [test_progs] fentry_test.bpf.o
clang: error: unknown argument: '--definitely-bogus-flag'
  SKIP-BPF     fentry_test.bpf.o
  GEN-SKEL [test_progs (signed)] fentry_test.lskel.h
libbpf: failed to open file '.../fentry_test.bpf.o': -ENOENT
Error: failed to link '.../fentry_test.bpf.o': No such file or directory (2)
  SKIP-SKEL    fentry_test.lskel.h

The GEN-SKEL line and the two error lines are new - the old recipe's
missing-input guard skipped silently before bpftool ever ran. The
final state is identical (outputs removed, build reports success and
continues):

prozak@powertower$ touch progs/fentry_test.c && $MK LLVM=
BPF_STRICT_BUILD=0 2>&1
...
  CLNG-BPF [test_progs] fentry_test.bpf.o
  GEN-SKEL [test_progs (signed)] fentry_test.lskel.h
...
  CLNG-BPF [test_progs-no_alu32] fentry_test.bpf.o
  GEN-SKEL [test_progs-no_alu32 (signed)] fentry_test.lskel.h
...
  CLNG-BPF [test_progs-cpuv4] fentry_test.bpf.o
  GEN-SKEL [test_progs-cpuv4 (signed)] fentry_test.lskel.h
...

While the output differs, on reflection the extra printing is
derivative - the compile failure is
already reported in full where it happens - so the next revision will
restore the quiet skip
(and with it, the 'behavior is unchanged' claim becomes accurate).

>
> > diff --git a/tools/testing/selftests/bpf/gen_bpf_skel.sh b/tools/testing/selftests/bpf/gen_bpf_skel.sh
> > new file mode 100755
> > index 0000000000000..e234f3a57d46b
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/gen_bpf_skel.sh
> > @@ -0,0 +1,93 @@
> > +#!/bin/bash
> > +# SPDX-License-Identifier: GPL-2.0
>
> [ ... ]
>
> > +# The bpftool binary is taken from $BPFTOOL (default: bpftool from PATH).
> > +# On failure all outputs and intermediates are removed and the script
> > +# exits non-zero; permissive-mode skipping is the caller's business
> > +# (see skip_on_fail in Makefile.buildvars).
>
> [Severity: Medium]
> Is the reference to Makefile.buildvars correct here?
>
> The file Makefile.buildvars does not exist. It looks like skip_on_fail is
> actually defined directly in tools/testing/selftests/bpf/Makefile.

Fair, will fix before re-spin.

>
> > +
> > +set -u
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260804170156.1709916-1-nickolay.lysenko@gmail.com?part=6

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line Mykola Lysenko
@ 2026-08-05 21:21   ` Eduard Zingerman
  0 siblings, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 21:21 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
> The generic '$(OUTPUT)/%:%.c' rule links with '$(LINK.c) $^', so
> every prerequisite of such a binary lands on the compiler driver's
> command line. That only works while none of them is a header: gcc
> tolerates a stray .h argument on a link line, but clang treats it as
> a request to precompile the header and fails with "cannot specify -o
> when generating multiple output files".
> 
> Filter headers out of the recipe, with a comment: header
> prerequisites on these targets are useful for dependency tracking -
> the next patch declares one - and the constraint is easy to
> rediscover the hard way.
> 
> Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines Mykola Lysenko
@ 2026-08-05 21:28   ` Eduard Zingerman
  2026-08-06  4:43     ` Mykola Lysenko
  0 siblings, 1 reply; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 21:28 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:

...

> @@ -480,11 +479,14 @@ BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)	\
>  
>  CLANG_CFLAGS = $(CLANG_SYS_INCLUDES)
>  
> -$(OUTPUT)/test_l4lb_noinline.o: BPF_CFLAGS += -fno-inline
> -$(OUTPUT)/test_xdp_noinline.o: BPF_CFLAGS += -fno-inline
> +$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
>  
> -$(OUTPUT)/flow_dissector_load.o: flow_dissector_load.h
> -$(OUTPUT)/cgroup_getset_retval_hooks.o: cgroup_getset_retval_hooks.h
> +# progs/cgroup_getset_retval_hooks.c includes this top-directory header,
> +# which the progs/*.h prerequisite of the BPF object rules does not
> +# cover.
> +$(addprefix $(OUTPUT)/,cgroup_getset_retval_hooks.bpf.o \
> +	$(addsuffix /cgroup_getset_retval_hooks.bpf.o,$(TEST_INST_SUBDIRS))): \
> +	cgroup_getset_retval_hooks.h

Would it be easier to just move cgroup_getset_retval_hooks.h under progs/?

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper Mykola Lysenko
  2026-08-04 17:21   ` sashiko-bot
@ 2026-08-05 21:33   ` Eduard Zingerman
  1 sibling, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 21:33 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
> With BPF_STRICT_BUILD=0, eleven recipes append the same "|| { remove
> the target, print a SKIP marker, report success }" tail, each spelled
> out inline. Factor the tail into skip_on_fail, parameterized by the
> SKIP tag, optional extra files to remove alongside the target and an
> optional note appended to the message - every call site keeps its
> exact message and behavior.
> 
> The permissive fragments of other shapes are not suffixes and keep
> their current form: the rsync --ignore-missing-args flags, the
> missing-input guards in the skeleton recipes, the link rule's
> wildcard handling and the test_kmods copy wrapper.
> 
> Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once Mykola Lysenko
@ 2026-08-05 22:03   ` Eduard Zingerman
  0 siblings, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 22:03 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
> $(VERIFICATION_CERT) and $(PRIVATE_KEY) come from a single genkey
> invocation, but a rule with two normal targets runs its recipe once
> per stale target - under -j both runs execute concurrently and the
> openssl invocations race on the same output files. Rewrite it as a
> pattern rule: pattern rules with multiple targets are implicitly
> grouped even before make 4.3 introduced grouped (&:) targets - the
> same workaround the test_kmods rule already uses.
> 
> Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe Mykola Lysenko
@ 2026-08-05 22:11   ` Eduard Zingerman
  0 siblings, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 22:11 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
> The verifier/tests.h recipe is a $(shell ...) expansion: the command
> runs while make expands the recipe line - including under make -n -
> its exit status is discarded, and the resulting (empty) expansion is
> what make actually executes. Turn it into a plain recipe.
> 
> Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script Mykola Lysenko
  2026-08-04 17:59   ` sashiko-bot
@ 2026-08-05 22:39   ` Eduard Zingerman
  1 sibling, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 22:39 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
> The four skeleton generation recipes (regular, light, signed light and
> linked) are near-identical five-step pipelines - link via "bpftool gen
> object" three times, compare the second and third results as a
> regression test for bpftool's determinism, generate the skeleton (and
> possibly subskeleton), remove intermediates - duplicated with small
> variations inside DEFINE_TEST_RUNNER_RULES, where every line pays the
> double-expansion escaping tax.
> 
> Move the pipeline into gen_bpf_skel.sh; the differences between the
> four variants become the --lskel, --sign and --subskel flags. Signing
> takes the key and certificate from $PRIVATE_KEY and $VERIFICATION_CERT
> in the environment, like the bpftool binary comes from $BPFTOOL. The
> distinct linked/llinked intermediate infixes are kept but derived
> inside the script: tests listed in LSKELS_EXTRA generate both a
> .skel.h and a .lskel.h from the same .bpf.o, and distinct intermediate
> names are what keeps parallel builds from racing. Build-log messages
> stay in the recipes with the usual $(call msg,...) helpers; behavior
> is unchanged.
> 
> Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
> Assisted-by: Claude:claude-fable-5 shellcheck
> Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

I need to print this out and hang it at my desk.

  ┌────────────┬─────────────────┬───────────────────────────┬─────┐
  │ expression │     x unset     │           x=""            │ x=v │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x-D}     │ D               │ `` (empty)                │ v   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x:-D}    │ D               │ D                         │ v   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x+A}     │ ``              │ A                         │ A   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x:+A}    │ ``              │ ``                        │ A   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x=D}     │ D (and assigns) │ `` (no assign — x is set) │ v   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x:=D}    │ D (and assigns) │ D (and assigns)           │ v   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x?msg}   │ abort           │ ``                        │ v   │
  ├────────────┼─────────────────┼───────────────────────────┼─────┤
  │ ${x:?msg}  │ abort           │ abort                     │ v   │
  └────────────┴─────────────────┴───────────────────────────┴─────┘

...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars Mykola Lysenko
@ 2026-08-05 23:34   ` Eduard Zingerman
  2026-08-06  4:51     ` Mykola Lysenko
  0 siblings, 1 reply; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-05 23:34 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:

...

Thank you for taking time to reshuffle the diff, such that the
Makefile.buildvars preserves the original relative order.
Much easier to review.

...

> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -3,60 +3,15 @@ include ../../../build/Build.include

...

> -GENHDR := $(GENDIR)/autoconf.h
>  PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It seems this can be removed as well, all uses are after
Makefile.buildvars is included. Or is it needed for one of the other
includes?

...

> @@ -220,21 +151,8 @@ ifeq ($(feature-llvm),1)

...

> -HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
> -RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
> +include Makefile.buildvars

It appears that this include can be moved up, to the lib.mk include.
In this case the duplicate definitions for `srctree` and `PKG_CONFIG`
can be removed (and both can reside in Makefile.buildvars).
Wdyt?

...

> --- /dev/null
> +++ b/tools/testing/selftests/bpf/Makefile.buildvars

...

> +PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config

...

> +ifeq ($(srctree),)
> +srctree := $(patsubst %/,%,$(dir $(CURDIR)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +endif

In case if shuffling includes around does not work, can we export
`srctree` in the main makefile and not copy it here?

...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines
  2026-08-05 21:28   ` Eduard Zingerman
@ 2026-08-06  4:43     ` Mykola Lysenko
  0 siblings, 0 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-06  4:43 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: bpf, ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Wed, Aug 5, 2026 at 2:28 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
>
> ...
>
> > @@ -480,11 +479,14 @@ BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)     \
> >
> >  CLANG_CFLAGS = $(CLANG_SYS_INCLUDES)
> >
> > -$(OUTPUT)/test_l4lb_noinline.o: BPF_CFLAGS += -fno-inline
> > -$(OUTPUT)/test_xdp_noinline.o: BPF_CFLAGS += -fno-inline
> > +$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
> >
> > -$(OUTPUT)/flow_dissector_load.o: flow_dissector_load.h
> > -$(OUTPUT)/cgroup_getset_retval_hooks.o: cgroup_getset_retval_hooks.h
> > +# progs/cgroup_getset_retval_hooks.c includes this top-directory header,
> > +# which the progs/*.h prerequisite of the BPF object rules does not
> > +# cover.
> > +$(addprefix $(OUTPUT)/,cgroup_getset_retval_hooks.bpf.o \
> > +     $(addsuffix /cgroup_getset_retval_hooks.bpf.o,$(TEST_INST_SUBDIRS))): \
> > +     cgroup_getset_retval_hooks.h
>
> Would it be easier to just move cgroup_getset_retval_hooks.h under progs/?

Yes, that is simpler, thanks - will do in v6.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars
  2026-08-05 23:34   ` Eduard Zingerman
@ 2026-08-06  4:51     ` Mykola Lysenko
  0 siblings, 0 replies; 23+ messages in thread
From: Mykola Lysenko @ 2026-08-06  4:51 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: bpf, ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

On Wed, Aug 5, 2026 at 4:34 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:
>
> ...
>
> Thank you for taking time to reshuffle the diff, such that the
> Makefile.buildvars preserves the original relative order.
> Much easier to review.

Thank you for taking time to review!

>
> ...
>
> > --- a/tools/testing/selftests/bpf/Makefile
> > +++ b/tools/testing/selftests/bpf/Makefile
> > @@ -3,60 +3,15 @@ include ../../../build/Build.include
>
> ...
>
> > -GENHDR := $(GENDIR)/autoconf.h
> >  PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
>    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> It seems this can be removed as well, all uses are after
> Makefile.buildvars is included. Or is it needed for one of the other
> includes?

It is not - none of the earlier includes reference PKG_CONFIG (or
srctree), and the remaining uses in the top Makefile are all
recipe-time, after the include.

>
> ...
>
> > @@ -220,21 +151,8 @@ ifeq ($(feature-llvm),1)
>
> ...
>
> > -HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
> > -RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
> > +include Makefile.buildvars
>
> It appears that this include can be moved up, to the lib.mk include.
> In this case the duplicate definitions for `srctree` and `PKG_CONFIG`
> can be removed (and both can reside in Makefile.buildvars).
> Wdyt?

Agreed, will do in v6.

>
> ...
>
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/Makefile.buildvars
>
> ...
>
> > +PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
>
> ...
>
> > +ifeq ($(srctree),)
> > +srctree := $(patsubst %/,%,$(dir $(CURDIR)))
> > +srctree := $(patsubst %/,%,$(dir $(srctree)))
> > +srctree := $(patsubst %/,%,$(dir $(srctree)))
> > +srctree := $(patsubst %/,%,$(dir $(srctree)))
> > +endif
>
> In case if shuffling includes around does not work, can we export
> `srctree` in the main makefile and not copy it here?

Not needed with the above. Thanks!

>
> ...

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make
  2026-08-04 17:01 ` [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make Mykola Lysenko
@ 2026-08-06  8:02   ` Eduard Zingerman
  0 siblings, 0 replies; 23+ messages in thread
From: Eduard Zingerman @ 2026-08-06  8:02 UTC (permalink / raw)
  To: Mykola Lysenko, bpf
  Cc: ast, daniel, andrii, martin.lau, song, yonghong.song, jolsa,
	memxor

[-- Attachment #1: Type: text/plain, Size: 9872 bytes --]

On Tue, 2026-08-04 at 10:01 -0700, Mykola Lysenko wrote:

...

> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 58c567a22..af3709d51 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile

...

> @@ -262,6 +249,35 @@ $(OUTPUT)/test_maps: $(TESTING_HELPERS)
>  $(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
>  $(OUTPUT)/xsk.o: $(BPFOBJ)
>
> +# All helper objects a runner instance links, pre-built here so runner
> +# sub-makes sharing $(OUTPUT) never compile them concurrently.
> +HELPER_OBJS := $(TESTING_HELPERS) $(CGROUP_HELPERS) $(UNPRIV_HELPERS)	\
> +	       $(TRACE_HELPERS) $(JSON_WRITER) $(CAP_HELPERS)		\
> +	       $(NETWORK_HELPERS) $(OUTPUT)/usdt_1.o $(OUTPUT)/usdt_2.o	\
> +	       $(OUTPUT)/xsk.o
> +
> +# $(BPFTOOL) is a prerequisite because its sub-make is what installs
> +# libbpf's internal headers (bpf/hashmap.h, bpf/libbpf_internal.h) into
> +# $(INCLUDE_DIR); without it, helpers including those headers race the
> +# install and can silently pick up the source-tree copies instead.
> +#
> +# Invariant: this list must be a superset of every prerequisite the
> +# runner's EXTRA_OBJS rule attaches to these same objects (EXTRA_HDRS,
> +# both tests.h flavors, $(BPFOBJ)). It guarantees the helpers built
> +# here are never older than a runner-side prerequisite, so the
> +# unflavored test_progs and test_maps sub-makes, which share $(OUTPUT),
> +# treat them as up to date instead of both recompiling the same object
> +# file concurrently.
> +$(HELPER_OBJS): flow_dissector_load.h ip_check_defrag_frags.h		\
> +		$(VERIFY_SIG_HDR) $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)\
> +		prog_tests/tests.h map_tests/tests.h $(BPFOBJ) $(BPFTOOL)
> +

Tbh, I don't understand what this comment is attempting to convey and
maintaining the invariant would be taxing going forward. LLM is also
trying to convince me that several dependencies are missing from the
list:

  test_progs.o btf_helpers.o sysctl_helpers.o netlink_helpers.o
  jit_disasm_helpers.o io_helpers.o test_loader.o disasm.o
  disasm_helpers.o bpftool_helpers.o find_bit.o

Which would form a race when compiling test_progs and test_maps that
both share the same $(OUTPUT) directory. This seem plausible.

How about a change as in the attached patch? (squashed with this patch).
It sidesteps a previously discussed constraint to keep refactoring to
the minimum, but HELPER_OBJS is just too ugly to pass.

> +$(OUTPUT)/flow_dissector_load: flow_dissector_load.h
> +
> +# Tool sub-builds: bpftool, libbpf (host and, when cross-compiling,
> +# target variants) and resolve_btfids. Deliberate asymmetry: the host
> +# libbpf builds without sanitizers and with the host toolchain.

Nit: is this comment necessary?

>  $(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile)    \
>  		    $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
>  	$(Q)$(MAKE) $(submake_extras)  -C $(BPFTOOLDIR)			       \

...

> @@ -672,81 +413,125 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)

...

> +# Everything a runner instance references but does not know how to build.
> +RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL)		\
> +		  $(TRUNNER_BPFTOOL) $(RESOLVE_BTFIDS)			\
> +		  $(OUTPUT)/veristat					\
> +		  $(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT)	\
> +		  $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)		\
> +		  prog_tests/tests.h map_tests/tests.h			\
> +		  $(HELPER_OBJS)					\
> +		  $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
> +		  $(OUTPUT)/xdp_synproxy $(OUTPUT)/sign-file		\
> +		  $(OUTPUT)/uprobe_multi $(TEST_KMOD_TARGETS)
> +
> +FORCE:

Nit: please drop a line here, saying that the main makefile does not
     know if $(OUTPUT)/test_progs is stale, hence FORCE to force
     delegation to Makefile.runner.

> +
> +# The default flavor's skeletons are also inputs of bench, test_maps,
> +# xskxceiver, xdp_* and test_cpp; "default-skels" builds just those
> +# without the test_progs test objects and binary.
> +.PHONY: default-skels
> +default-skels: $(RUNNER_PREREQS)
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR= TESTS_DIR=prog_tests \
> +		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
> +		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v3'		\
> +		BPF_DEFINES=-DENABLE_ATOMICS_TESTS skels
> +
> +$(OUTPUT)/test_progs: $(RUNNER_PREREQS) default-skels FORCE
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR= TESTS_DIR=prog_tests \
> +		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
> +		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v3'		\
> +		BPF_DEFINES=-DENABLE_ATOMICS_TESTS

Is there a way to reduce copy-paste between default-skels and
test_progs? Why does test_progs depend on default-skels?

> +
> +$(OUTPUT)/test_progs-no_alu32: $(RUNNER_PREREQS) FORCE
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=no_alu32 TESTS_DIR=prog_tests \
> +		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
> +		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v2'
> +
>  ifneq ($(CLANG_CPUV4),)
> -TRUNNER_BPF_BUILD_RULE := CLANG_CPUV4_BPF_BUILD_RULE
> -TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS) -DENABLE_ATOMICS_TESTS
> -$(eval $(call DEFINE_TEST_RUNNER,test_progs,cpuv4))
> +$(OUTPUT)/test_progs-cpuv4: $(RUNNER_PREREQS) FORCE
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=cpuv4 TESTS_DIR=prog_tests \
> +		BPF_CC='$(CLANG)' BPF_CC_MSG=CLNG-BPF BPF_SYS_INCLUDES='$(CLANG_SYS_INCLUDES)'	\
> +		BPF_CC_FLAGS='-O2 $(BPF_TARGET_ENDIAN) -mcpu=v4'		\
> +		BPF_DEFINES=-DENABLE_ATOMICS_TESTS
>  endif
>
> -# Define test_progs BPF-GCC-flavored test runner.
>  ifneq ($(BPF_GCC),)
> -TRUNNER_BPF_BUILD_RULE := GCC_BPF_BUILD_RULE
> -TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(call get_sys_includes,gcc,)
> -$(eval $(call DEFINE_TEST_RUNNER,test_progs,bpf_gcc))
> +# Sys includes come from the *host* gcc, not $(BPF_GCC) - bpf-gcc's own
> +# search path lacks the host's asm-generic/ headers.
> +GCC_SYS_INCLUDES := $(call get_sys_includes,gcc,)
> +
> +$(OUTPUT)/test_progs-bpf_gcc: $(RUNNER_PREREQS) FORCE
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_progs FLAVOR=bpf_gcc TESTS_DIR=prog_tests \
> +		BPF_CC='$(BPF_GCC)' BPF_CC_MSG=GCC-BPF				\
> +		BPF_SYS_INCLUDES='$(GCC_SYS_INCLUDES)'				\
> +		BPF_CC_FLAGS='-DBPF_NO_PRESERVE_ACCESS_INDEX -Wno-attributes -O2'
>  endif

Did you test the bpf_gcc flavor?
I have a recipe to build the toolchain somewhere, if you need it.

>
> -# Define test_maps test runner.
> -TRUNNER_TESTS_DIR := map_tests
> -TRUNNER_BPF_PROGS_DIR := progs
> -TRUNNER_EXTRA_SOURCES := test_maps.c
> -TRUNNER_LIB_SOURCES :=
> -TRUNNER_EXTRA_FILES :=
> -TRUNNER_BPF_BUILD_RULE := $$(error no BPF objects should be built)
> -TRUNNER_BPF_CFLAGS :=
> -$(eval $(call DEFINE_TEST_RUNNER,test_maps))
> -
> -# Define test_verifier test runner.
> -# It is much simpler than test_maps/test_progs and sufficiently different from
> -# them (e.g., test.h is using completely pattern), that it's worth just
> -# explicitly defining all the rules explicitly.
> +# test_maps compiles map_tests/*.c against the default flavor's skeletons
> +$(OUTPUT)/test_maps: $(RUNNER_PREREQS) map_tests/tests.h default-skels FORCE
> +	+$(Q)$(RUNNER_MAKE) RUNNER=test_maps FLAVOR= TESTS_DIR=map_tests
> +
> +# Default-flavor BPF objects and skeletons consumed by targets in this
> +# Makefile (and by lib.mk's install rule) are produced by the runner
> +# sub-make above; the empty recipe plus the order-only prerequisite
> +# makes them available here without duplicating the runner's rules.
> +DEFAULT_BPF_OBJS := $(patsubst progs/%.c,$(OUTPUT)/%.bpf.o,$(wildcard progs/*.c))
> +TEST_GEN_FILES += $(DEFAULT_BPF_OBJS)

--- 8< --------------------------------------------------------------------------

> +BENCH_SKELS := $(addprefix $(OUTPUT)/,					\
> +	       test_overhead.skel.h trigger_bench.skel.h		\
> +	       ringbuf_bench.skel.h perfbuf_bench.skel.h		\
> +	       bloom_filter_bench.skel.h bpf_loop_bench.skel.h		\
> +	       strncmp_bench.skel.h bpf_hashmap_full_update_bench.skel.h\
> +	       local_storage_bench.skel.h				\
> +	       local_storage_rcu_tasks_trace_bench.skel.h		\
> +	       bench_local_storage_create.skel.h			\
> +	       bpf_hashmap_lookup.skel.h htab_mem_bench.skel.h		\
> +	       crypto_bench.skel.h bench_sockmap_prog.skel.h		\
> +	       lpm_trie_bench.skel.h lpm_trie_map.skel.h		\
> +	       bpf_nop_bench.skel.h xdp_lb_bench.skel.h)
> +TOP_SKELS := $(addprefix $(OUTPUT)/,xsk_xdp_progs.skel.h		\
> +	     xdp_hw_metadata.skel.h xdp_features.skel.h			\
> +	     test_core_extern.skel.h) $(BENCH_SKELS)
> +
> +# Empty-recipe delegation: "default-skels" (re)builds the file via the
> +# runner sub-make, after which make re-stats the target, so dependents
> +# see fresh timestamps within the same invocation. Dependents still
> +# rebuild on mtime only - the phony prerequisite does not cascade.
> +$(DEFAULT_BPF_OBJS) $(TOP_SKELS): default-skels ;

-------------------------------------------------------------------------- >8 ---

Note, the above two lists can be replaced with the following two rules:

  $(OUTPUT)/%.skel.h: default-skels ;
  $(DEFAULT_BPF_OBJS): default-skels ;

> +
> +# Standalone test binaries
> +
> +# test_verifier is much simpler than test_maps/test_progs and
> +# sufficiently different from them (e.g., its tests.h uses a completely
> +# different pattern), that it's worth just defining its rules explicitly.
>  verifier/tests.h: verifier/*.c
>  	$(Q)( cd verifier/; \
>  		  echo '/* Generated header, do not edit */'; \

...

[-- Attachment #2: common-objects.diff --]
[-- Type: text/x-patch, Size: 10684 bytes --]

From 25addc8be0d5548652a9a93504a1245157faa845 Mon Sep 17 00:00:00 2001
From: Eduard Zingerman <eddyz87@gmail.com>
Date: Wed, 5 Aug 2026 23:35:14 -0700
Subject: [PATCH] selftests/bpf: build the runner userspace objects in the main
 Makefile

Squash into "selftests/bpf: build each test runner instance in its own
sub-make".

Instead of maintaining an invariant list of source files to pre-build
in the main makefile and having the rules to build same objects in the
Makefile.runner, build these objects explicitly in the main makefile
and drop the rules from Makefile.runner. Let each runner instance link
them as plain prerequisites. The lists live in Makefile.buildvars.

Gone from Makefile.runner: the invariant note, EXTRA_SOURCES,
LIB_SOURCES, EXTRA_OBJS, LIB_OBJS and both compile rules.
EXTRA_HDRS shrinks to the generated headers and is renamed
accordingly. The flavored instances no longer build their own copies
of shared prerequesties.

With no rule left for those objects, a missing one would match make's
built-in %.o rule and be rebuilt with default flags, so disable the
built-in rules (The `MAKEFLAGS += -r` thingy).
---
 tools/testing/selftests/bpf/Makefile          | 39 ++++++-------
 .../testing/selftests/bpf/Makefile.buildvars  | 17 ++++++
 tools/testing/selftests/bpf/Makefile.runner   | 55 +++++--------------
 3 files changed, 46 insertions(+), 65 deletions(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index af3709d51f67..dc14a89ed55b 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -245,32 +245,25 @@ $(OUTPUT)/test_sock_fields: $(CGROUP_HELPERS) $(TESTING_HELPERS)
 $(OUTPUT)/test_tag: $(TESTING_HELPERS)
 $(OUTPUT)/test_lirc_mode2_user: $(TESTING_HELPERS)
 $(OUTPUT)/flow_dissector_load: $(TESTING_HELPERS)
-$(OUTPUT)/test_maps: $(TESTING_HELPERS)
 $(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
-$(OUTPUT)/xsk.o: $(BPFOBJ)
-
-# All helper objects a runner instance links, pre-built here so runner
-# sub-makes sharing $(OUTPUT) never compile them concurrently.
-HELPER_OBJS := $(TESTING_HELPERS) $(CGROUP_HELPERS) $(UNPRIV_HELPERS)	\
-	       $(TRACE_HELPERS) $(JSON_WRITER) $(CAP_HELPERS)		\
-	       $(NETWORK_HELPERS) $(OUTPUT)/usdt_1.o $(OUTPUT)/usdt_2.o	\
-	       $(OUTPUT)/xsk.o
 
+# The userspace objects the runner instances link (RUNNER_OBJS, see
+# Makefile.buildvars) are built here, once, so that no runner sub-make
+# ever writes a file this Makefile also builds.
+#
 # $(BPFTOOL) is a prerequisite because its sub-make is what installs
 # libbpf's internal headers (bpf/hashmap.h, bpf/libbpf_internal.h) into
-# $(INCLUDE_DIR); without it, helpers including those headers race the
-# install and can silently pick up the source-tree copies instead.
-#
-# Invariant: this list must be a superset of every prerequisite the
-# runner's EXTRA_OBJS rule attaches to these same objects (EXTRA_HDRS,
-# both tests.h flavors, $(BPFOBJ)). It guarantees the helpers built
-# here are never older than a runner-side prerequisite, so the
-# unflavored test_progs and test_maps sub-makes, which share $(OUTPUT),
-# treat them as up to date instead of both recompiling the same object
-# file concurrently.
-$(HELPER_OBJS): flow_dissector_load.h ip_check_defrag_frags.h		\
-		$(VERIFY_SIG_HDR) $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)\
-		prog_tests/tests.h map_tests/tests.h $(BPFOBJ) $(BPFTOOL)
+# $(INCLUDE_DIR); without it, trace_helpers.c races the install and can
+# silently pick up the source-tree copies instead.
+$(RUNNER_OBJS): $(VERIFY_SIG_HDR) prog_tests/tests.h map_tests/tests.h	\
+		$(BPFOBJ) $(BPFTOOL)
+
+# Some utility functions use LLVM libraries
+$(OUTPUT)/jit_disasm_helpers.o: CFLAGS += $(LLVM_CFLAGS)
+
+$(OUTPUT)/find_bit.o: $(TOOLSDIR)/lib/find_bit.c
+	$(call msg,CC,,$@)
+	$(Q)$(CC) $(CFLAGS) -c $< -o $@
 
 $(OUTPUT)/flow_dissector_load: flow_dissector_load.h
 
@@ -446,7 +439,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL)		\
 		  $(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT)	\
 		  $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)		\
 		  prog_tests/tests.h map_tests/tests.h			\
-		  $(HELPER_OBJS)					\
+		  $(RUNNER_OBJS)					\
 		  $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
 		  $(OUTPUT)/xdp_synproxy $(OUTPUT)/sign-file		\
 		  $(OUTPUT)/uprobe_multi $(TEST_KMOD_TARGETS)
diff --git a/tools/testing/selftests/bpf/Makefile.buildvars b/tools/testing/selftests/bpf/Makefile.buildvars
index 044e0d73b09b..bb95cff65324 100644
--- a/tools/testing/selftests/bpf/Makefile.buildvars
+++ b/tools/testing/selftests/bpf/Makefile.buildvars
@@ -169,3 +169,20 @@ ifneq ($(CLANG_HAS_ARENA_ASAN),)
 LIBARENA_ASAN_SKEL := libarena/libarena_asan.skel.h
 CFLAGS += -DHAS_BPF_ARENA_ASAN
 endif
+
+RUNNER_OBJS-test_progs := $(addprefix $(OUTPUT)/,			\
+	test_progs.o cgroup_helpers.o trace_helpers.o			\
+	network_helpers.o testing_helpers.o btf_helpers.o		\
+	cap_helpers.o unpriv_helpers.o sysctl_helpers.o			\
+	netlink_helpers.o jit_disasm_helpers.o io_helpers.o		\
+	test_loader.o xsk.o disasm.o disasm_helpers.o			\
+	json_writer.o bpftool_helpers.o usdt_1.o usdt_2.o)
+RUNNER_OBJS-test_maps := $(addprefix $(OUTPUT)/,			\
+	test_maps.o testing_helpers.o)
+
+# find_bit.c is the only runner source outside this directory,
+# so it needs a rule of its own
+RUNNER_LIB_OBJS-test_progs := $(OUTPUT)/find_bit.o
+
+RUNNER_OBJS := $(sort $(RUNNER_OBJS-test_progs) $(RUNNER_OBJS-test_maps)\
+		      $(RUNNER_LIB_OBJS-test_progs))
diff --git a/tools/testing/selftests/bpf/Makefile.runner b/tools/testing/selftests/bpf/Makefile.runner
index 91e8a2df468f..ed6262dc13e3 100644
--- a/tools/testing/selftests/bpf/Makefile.runner
+++ b/tools/testing/selftests/bpf/Makefile.runner
@@ -7,9 +7,9 @@
 # so everything here is written in plain make - no $$-escaping, no eval,
 # no per-flavor guards. This file is always invoked by the main Makefile,
 # never directly: all shared prerequisites (libbpf, bpftool, vmlinux.h,
-# veristat, helper objects, signing key, generated tests.h, extra
-# binaries) are built by the main Makefile *before* this one runs and are
-# referenced below as plain files.
+# veristat, helper objects, signing key, generated
+# tests.h, extra binaries) are built by the main Makefile *before* this
+# one runs and are referenced below as plain files.
 #
 # Parameters (all passed on the sub-make command line):
 #   OUTPUT      absolute path of the selftests output directory
@@ -63,6 +63,11 @@ all: $(OUTPUT)/$(BINARY)
 # Delete partially updated (corrupted) files on error
 .DELETE_ON_ERROR:
 
+# No built-in rules: every file built here has an explicit rule, and the
+# objects that come from the main Makefile have to be an error when
+# missing rather than a silent rebuild by the built-in %.o recipes.
+MAKEFLAGS += -r
+
 # Permissive skip-on-failure of individual tests applies to test_progs and
 # its flavors only; test_maps uses strong cross-object references and keeps
 # strict semantics even when BPF_STRICT_BUILD=0.
@@ -77,15 +82,7 @@ $(RDIR):
 # ---------------------------------------------------------------------
 
 ifeq ($(RUNNER),test_progs)
-EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c		\
-		 network_helpers.c testing_helpers.c btf_helpers.c	\
-		 cap_helpers.c unpriv_helpers.c sysctl_helpers.c	\
-		 netlink_helpers.c jit_disasm_helpers.c io_helpers.c	\
-		 test_loader.c xsk.c disasm.c disasm_helpers.c		\
-		 json_writer.c bpftool_helpers.c usdt_1.c usdt_2.c
-LIB_SOURCES := find_bit.c
-EXTRA_HDRS := flow_dissector_load.h ip_check_defrag_frags.h		\
-	      $(VERIFY_SIG_HDR) $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)
+GENERATED_HDRS := $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL)
 EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
 	       $(OUTPUT)/xdp_synproxy $(OUTPUT)/sign-file		\
 	       $(OUTPUT)/uprobe_multi					\
@@ -95,25 +92,9 @@ EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/liburandom_read.so	\
 	       $(wildcard progs/*.bpf.o)
 endif
 
-ifeq ($(RUNNER),test_maps)
-EXTRA_SOURCES := test_maps.c testing_helpers.c
-endif
-
-# Note on the unflavored runners, which share $(OUTPUT) with the main
-# Makefile: helper objects the main Makefile also uses (and lists in the
-# runner prerequisites) are already built and fresh when a runner starts,
-# so the rules below find them up to date and never write them. This
-# holds because the main Makefile's helper-object rule depends on a
-# superset of the prerequisites the EXTRA_OBJS rule uses here - keep
-# the two lists in sync (see the invariant comment there).
-
-TESTS_HDR := $(TESTS_DIR)/tests.h
-
 TEST_SRCS  := $(notdir $(wildcard $(TESTS_DIR)/*.c))
 TEST_OBJS  := $(patsubst %.c,$(RDIR)/%.test.o,$(TEST_SRCS))
 TEST_DEPS  := $(TEST_OBJS:.o=.d)
-EXTRA_OBJS := $(patsubst %.c,$(RDIR)/%.o,$(filter %.c,$(EXTRA_SOURCES)))
-LIB_OBJS   := $(patsubst %.c,$(RDIR)/%.o,$(LIB_SOURCES))
 
 # ---------------------------------------------------------------------
 # BPF objects and skeletons
@@ -270,22 +251,11 @@ $(TEST_OBJS): $(RDIR)/%.test.o: $(TESTS_DIR)/%.c | $(RDIR)/%.test.d
 		$(RESOLVE_BTFIDS) --patch_btfids $@.BTF_ids $@;		\
 		fi)
 
-$(TEST_DEPS): $(RDIR)/%.test.d: $(TESTS_DIR)/%.c $(EXTRA_HDRS) $(BPFOBJ) \
+$(TEST_DEPS): $(RDIR)/%.test.d: $(TESTS_DIR)/%.c $(GENERATED_HDRS) $(BPFOBJ) \
 	      | $(RDIR) $(ALL_SKELS)
 
 include $(wildcard $(TEST_DEPS))
 
-# Some utility functions use LLVM libraries
-$(RDIR)/jit_disasm_helpers.o: CFLAGS += $(LLVM_CFLAGS)
-
-$(EXTRA_OBJS): $(RDIR)/%.o: %.c $(EXTRA_HDRS) $(TESTS_HDR) $(BPFOBJ) | $(RDIR)
-	$(call msg,EXT-OBJ,$(BINARY),$@)
-	$(Q)$(CC) $(CFLAGS) -c $< -o $@
-
-$(LIB_OBJS): $(RDIR)/%.o: $(TOOLSDIR)/lib/%.c | $(RDIR)
-	$(call msg,LIB-OBJ,$(BINARY),$@)
-	$(Q)$(CC) $(CFLAGS) -c $< -o $@
-
 # Non-flavored in-srctree builds receive special treatment, in particular
 # we do not need to copy extra resources (see e.g. test_btf_dump_case()).
 .PHONY: extras
@@ -303,10 +273,11 @@ endif
 # relinks the runner - while the full set stays order-only to drive the
 # build attempts, and the wildcard in the recipe picks up the survivors
 # at link time.
-# Prerequisite order is also link order (test objects, extra objects,
+# Prerequisite order is also link order (test objects, runner objects,
 # libbpf, lib objects).
 $(OUTPUT)/$(BINARY): $(if $(PERMISSIVE_TESTS),$(wildcard $(TEST_OBJS)),$(TEST_OBJS)) \
-		     $(EXTRA_OBJS) $(BPFOBJ) $(LIB_OBJS)		\
+		     $(RUNNER_OBJS-$(RUNNER)) $(BPFOBJ)			\
+		     $(RUNNER_LIB_OBJS-$(RUNNER))			\
 		     $(TRUNNER_BPFTOOL) $(OUTPUT)/veristat		\
 		     | extras $(BPF_OBJS)				\
 		     $(if $(PERMISSIVE_TESTS),$(TEST_OBJS))
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2026-08-06  8:02 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04 17:01 [RFC PATCH bpf-next v5 0/8] selftests/bpf: restructure the Makefile as a layered build Mykola Lysenko
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 1/8] selftests/bpf: keep headers off the generic link command line Mykola Lysenko
2026-08-05 21:21   ` Eduard Zingerman
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 2/8] selftests/bpf: drop stale and no-op lines Mykola Lysenko
2026-08-05 21:28   ` Eduard Zingerman
2026-08-06  4:43     ` Mykola Lysenko
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 3/8] selftests/bpf: factor the permissive-mode skip suffix into a helper Mykola Lysenko
2026-08-04 17:21   ` sashiko-bot
2026-08-05  0:13     ` Mykola Lysenko
2026-08-05 21:33   ` Eduard Zingerman
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 4/8] selftests/bpf: generate the signing key and certificate once Mykola Lysenko
2026-08-05 22:03   ` Eduard Zingerman
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 5/8] selftests/bpf: generate verifier/tests.h in a regular recipe Mykola Lysenko
2026-08-05 22:11   ` Eduard Zingerman
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 6/8] selftests/bpf: extract BPF skeleton generation into a helper script Mykola Lysenko
2026-08-04 17:59   ` sashiko-bot
2026-08-05  1:40     ` Mykola Lysenko
2026-08-05 22:39   ` Eduard Zingerman
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 7/8] selftests/bpf: move shared build definitions into Makefile.buildvars Mykola Lysenko
2026-08-05 23:34   ` Eduard Zingerman
2026-08-06  4:51     ` Mykola Lysenko
2026-08-04 17:01 ` [RFC PATCH bpf-next v5 8/8] selftests/bpf: build each test runner instance in its own sub-make Mykola Lysenko
2026-08-06  8:02   ` Eduard Zingerman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).