LLC            ?= llc
 LLVM_OBJCOPY   ?= llvm-objcopy
 BPF_GCC                ?= $(shell command -v bpf-gcc;)
-CFLAGS += -g -Wall -O2 $(GENFLAGS) -I$(CURDIR) -I$(APIDIR) -I$(LIBDIR)  \
-         -I$(BPFDIR) -I$(GENDIR) -I$(TOOLSINCDIR)                      \
+CFLAGS += -g -Wall -O2 $(GENFLAGS) -I$(CURDIR) -I$(APIDIR)             \
+         -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) -I$(TOOLSINCDIR)     \
          -Dbpf_prog_load=bpf_prog_test_load                            \
          -Dbpf_load_program=bpf_test_load_program
 LDLIBS += -lcap -lelf -lz -lrt -lpthread
 override define CLEAN
        $(call msg,CLEAN)
        $(RM) -r $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) $(EXTRA_CLEAN)
-       $(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/ clean
 endef
 
 include ../lib.mk
 
+SCRATCH_DIR := $(OUTPUT)/tools
+BUILD_DIR := $(SCRATCH_DIR)/build
+INCLUDE_DIR := $(SCRATCH_DIR)/include
+BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
+
 # Define simple and short `make test_progs`, `make test_sysctl`, etc targets
 # to build individual tests.
 # NOTE: Semicolon at the end is critical to override lib.mk's default static
        $(call msg,BINARY,,$@)
        $(CC) -o $@ $< -Wl,--build-id
 
-$(OUTPUT)/test_stub.o: test_stub.c
+$(OUTPUT)/test_stub.o: test_stub.c $(BPFOBJ)
        $(call msg,CC,,$@)
        $(CC) -c $(CFLAGS) -o $@ $<
 
                               /sys/kernel/btf/vmlinux                  \
                               /boot/vmlinux-$(shell uname -r)
 VMLINUX_BTF:= $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))
-.PHONY: $(OUTPUT)/runqslower
-$(OUTPUT)/runqslower: force
+$(OUTPUT)/runqslower: $(BPFOBJ)
        $(Q)$(MAKE) $(submake_extras) -C $(TOOLSDIR)/bpf/runqslower     \
-                   OUTPUT=$(OUTPUT)/tools/ VMLINUX_BTF=$(VMLINUX_BTF)
-
-BPFOBJ := $(OUTPUT)/libbpf.a
+                   OUTPUT=$(SCRATCH_DIR)/ VMLINUX_BTF=$(VMLINUX_BTF)   \
+                   BPFOBJ=$(BPFOBJ) BPF_INCLUDE=$(INCLUDE_DIR)
 
 $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/test_stub.o $(BPFOBJ)
 
 $(OUTPUT)/test_sock_fields: cgroup_helpers.c
 $(OUTPUT)/test_sysctl: cgroup_helpers.c
 
-.PHONY: force
-
-# force a rebuild of BPFOBJ when its dependencies are updated
-force:
-
-DEFAULT_BPFTOOL := $(OUTPUT)/tools/sbin/bpftool
+DEFAULT_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
 BPFTOOL ?= $(DEFAULT_BPFTOOL)
+$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BUILD_DIR)/bpftool
+       $(Q)$(MAKE) $(submake_extras)  -C $(BPFTOOLDIR)                 \
+                   OUTPUT=$(BUILD_DIR)/bpftool/                        \
+                   prefix= DESTDIR=$(SCRATCH_DIR)/ install
 
-$(DEFAULT_BPFTOOL): force
-       $(Q)$(MAKE) $(submake_extras)  -C $(BPFTOOLDIR)                       \
-                   prefix= DESTDIR=$(OUTPUT)/tools/ install
-
-$(BPFOBJ): force
-       $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(OUTPUT)/
+$(BPFOBJ): $(wildcard $(BPFDIR)/*.c $(BPFDIR)/*.h $(BPFDIR)/Makefile)          \
+          ../../../include/uapi/linux/bpf.h                                   \
+          | $(INCLUDE_DIR) $(BUILD_DIR)/libbpf
+       $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
+               DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
 
-BPF_HELPERS := $(OUTPUT)/bpf_helper_defs.h $(wildcard $(BPFDIR)/bpf_*.h)
-$(OUTPUT)/bpf_helper_defs.h: $(BPFOBJ)
-       $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR)                            \
-                   OUTPUT=$(OUTPUT)/ $(OUTPUT)/bpf_helper_defs.h
+$(BUILD_DIR)/libbpf $(BUILD_DIR)/bpftool $(INCLUDE_DIR):
+       $(call msg,MKDIR,,$@)
+       mkdir -p $@
 
 # Get Clang's default includes on this system, as opposed to those seen by
 # '-target bpf'. This fixes "missing" files on some architectures/distros,
 
 CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
 BPF_CFLAGS = -g -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN)                  \
-            -I$(OUTPUT) -I$(CURDIR) -I$(CURDIR)/include/uapi           \
-            -I$(APIDIR) -I$(LIBDIR) -I$(BPFDIR) -I$(abspath $(OUTPUT)/../usr/include)
+            -I$(INCLUDE_DIR) -I$(CURDIR) -I$(CURDIR)/include/uapi      \
+            -I$(APIDIR) -I$(abspath $(OUTPUT)/../usr/include)
 
 CLANG_CFLAGS = $(CLANG_SYS_INCLUDES) \
               -Wno-compare-distinct-pointer-types
 $(TRUNNER_BPF_OBJS): $(TRUNNER_OUTPUT)/%.o:                            \
                     $(TRUNNER_BPF_PROGS_DIR)/%.c                       \
                     $(TRUNNER_BPF_PROGS_DIR)/*.h                       \
-                    $$(BPF_HELPERS) | $(TRUNNER_OUTPUT)
+                    $$(BPFOBJ) | $(TRUNNER_OUTPUT)
        $$(call $(TRUNNER_BPF_BUILD_RULE),$$<,$$@,                      \
                                          $(TRUNNER_BPF_CFLAGS),        \
                                          $(TRUNNER_BPF_LDFLAGS))
        $(call msg,CXX,,$@)
        $(CXX) $(CFLAGS) $^ $(LDLIBS) -o $@
 
-EXTRA_CLEAN := $(TEST_CUSTOM_PROGS)                                    \
+EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(SCRATCH_DIR)                     \
        prog_tests/tests.h map_tests/tests.h verifier/tests.h           \
        feature                                                         \
-       $(addprefix $(OUTPUT)/,*.o *.skel.h no_alu32 bpf_gcc tools)
+       $(addprefix $(OUTPUT)/,*.o *.skel.h no_alu32 bpf_gcc)