# makefile environment variable defaults.
-# cc is the name of the C compiler, a file called <name>.c is C source code.
+# cc is the name of the C compiler, a file called <name>.c is C source code.
+# RT uses header integrated C source files, i.e. the source and the header are the same file
SHELL=/bin/bash
-#ECHO= echo -e
-#ECHO= echo
+# environment_RT_1.mk
+
ECHO := printf "%b\n"
+C_SOURCE_DIR := cc
+KMOD_SOURCE_DIR := cc
+
+C := gcc
+CFLAGS := -std=gnu11 -Wall -Wextra -Wpedantic -finput-charset=UTF-8
+CFLAGS += -MMD -MP
+CFLAGS += -I $(C_SOURCE_DIR)
+
+KMOD_CCFLAGS := -I $(KMOD_SOURCE_DIR)
+
+LIBNAME := $(PROJECT)
+LIBNAME := $(subst -,_,$(LIBNAME))
+
+LIBDIR := scratchpad
+LIBFILE := $(LIBDIR)/lib$(LIBNAME).a
+
+LINKFLAGS := -L$(LIBDIR) -L/lib64 -L/lib
-# sources found in these subdirectories:
-SRCDIR_LIST=cc
-LIBDIR := scratchpad
-LIBFILE := $(LIBDIR)/lib.a
-LINKFLAGS := -L$(LIBDIR) -L/lib64 -L/lib
-EXECDIR := scratchpad
+EXECDIR := scratchpad
-C=gcc
-CFLAGS=-std=gnu11 -Wall -Wextra -Wpedantic -finput-charset=UTF-8
-CFLAGS += -MMD -MP
-LINKFLAGS=-L$(LIBDIR) -L/lib64 -L/lib
.SUFFIXES:
.EXPORT_ALL_VARIABLES:
+.DELETE_ON_ERROR:
-SOURCE_DIR := cc
-BUILD_DIR := /lib/modules/$(shell uname -r)/build
-OUTPUT_DIR := $(abspath scratchpad/kmod) # dedicated kmod staging/build dir
+# allow overrides
+KMOD_SOURCE_DIR ?= cc
+
+# allow cross-compile override via KDIR; else default to running kernel
+KDIR ?=
+BUILD_DIR ?= $(if $(KDIR),$(KDIR),/lib/modules/$(shell uname -r)/build)
+
+# default, then canonicalize to absolute regardless of source
+OUTPUT_DIR ?= scratchpad/kmod
+OUTPUT_DIR := $(abspath $(OUTPUT_DIR))
# authored basenames (without suffix)
-BASE_LIST := $(patsubst %.kmod.c,%,$(notdir $(wildcard $(SOURCE_DIR)/*.kmod.c)))
+BASE_LIST := $(patsubst %.kmod.c,%,$(notdir $(wildcard $(KMOD_SOURCE_DIR)/*.kmod.c)))
# optional library sources (without suffix) to include inside modules
# set KMOD_INCLUDE_LIB=0 to disable
KMOD_INCLUDE_LIB ?= 1
ifeq ($(KMOD_INCLUDE_LIB),1)
-LIB_BASE := $(patsubst %.lib.c,%,$(notdir $(wildcard $(SOURCE_DIR)/*.lib.c)))
+LIB_BASE := $(patsubst %.lib.c,%,$(notdir $(wildcard $(KMOD_SOURCE_DIR)/*.lib.c)))
else
LIB_BASE :=
endif
.PHONY: information
information:
- @echo "SOURCE_DIR: " $(SOURCE_DIR)
+ @echo "KMOD_SOURCE_DIR: " $(KMOD_SOURCE_DIR)
@echo "BUILD_DIR: " $(BUILD_DIR)
@echo "OUTPUT_DIR: " $(OUTPUT_DIR)
@echo "BASE_LIST: " $(BASE_LIST)
@echo "KMOD_INCLUDE_LIB=" $(KMOD_INCLUDE_LIB)
ifndef BASE_LIST
-$(warning No *.kmod.c found under $(SOURCE_DIR); nothing to build)
+$(warning No *.kmod.c found under $(KMOD_SOURCE_DIR); nothing to build)
endif
-.PHONY: kmod
-kmod: _prepare modules
+# --- Parallel-safe preparation as real targets ---
-.PHONY: _prepare
-_prepare:
+# ensure the staging dir exists (order-only prereq)
+$(OUTPUT_DIR):
@mkdir -p "$(OUTPUT_DIR)"
- # fresh Kbuild control file — one obj per module; each module also links lib objs (if any)
+
+# generate the Kbuild control Makefile
+$(OUTPUT_DIR)/Makefile: | $(OUTPUT_DIR)
@{ \
printf "obj-m := %s\n" "$(foreach m,$(BASE_LIST),$(m).o)"; \
for m in $(BASE_LIST); do \
for lb in $(LIB_BASE); do printf " %s.lib.o" "$$lb"; done; \
printf "\n"; \
done; \
- } > "$(OUTPUT_DIR)/Makefile"
- # stage kmod sources (read-only authored dir, write only to OUTPUT_DIR)
- @for b in $(BASE_LIST); do \
- src="$(SOURCE_DIR)/$$b.kmod.c"; dst="$(OUTPUT_DIR)/$$b.kmod.c"; \
- echo "--- Stage: $$dst ---"; ln -sf "$$src" "$$dst" 2>/dev/null || cp "$$src" "$$dst"; \
- done
- # stage library sources (optional)
- @for b in $(LIB_BASE); do \
- src="$(SOURCE_DIR)/$$b.lib.c"; dst="$(OUTPUT_DIR)/$$b.lib.c"; \
- echo "--- Stage: $$dst ---"; ln -sf "$$src" "$$dst" 2>/dev/null || cp "$$src" "$$dst"; \
- done
+ } > "$@"
+
+# stage kmod sources (one rule per file; parallelizable)
+$(OUTPUT_DIR)/%.kmod.c: $(KMOD_SOURCE_DIR)/%.kmod.c | $(OUTPUT_DIR)
+ @echo "--- Stage: $@ ---"
+ @ln -sf "$<" "$@" 2>/dev/null || { rm -f "$@"; cp "$<" "$@"; }
+# stage library sources (optional; also parallelizable)
+$(OUTPUT_DIR)/%.lib.c: $(KMOD_SOURCE_DIR)/%.lib.c | $(OUTPUT_DIR)
+ @echo "--- Stage: $@ ---"
+ @ln -sf "$<" "$@" 2>/dev/null || { rm -f "$@"; cp "$<" "$@"; }
+
+# rebuild inputs for 'modules'
.PHONY: modules
modules: $(OUTPUT_DIR)/Makefile $(ALL_KMOD_C) $(ALL_LIB_C)
@echo "--- Invoking Kbuild for Modules: $(BASE_LIST) ---"
$(MAKE) -C "$(BUILD_DIR)" M="$(OUTPUT_DIR)" modules
+# top-level convenience target
+.PHONY: kmod
+kmod: modules
+
+
# quality-of-life: allow 'make scratchpad/kmod/foo.ko' after batch build
$(OUTPUT_DIR)/%.ko: modules
@true
@if [ -d "$(OUTPUT_DIR)" ]; then \
echo "--- Cleaning Kbuild Artifacts in $(OUTPUT_DIR) ---"; \
$(MAKE) -C "$(BUILD_DIR)" M="$(OUTPUT_DIR)" clean; \
- find "$(OUTPUT_DIR)" -maxdepth 1 -type l -name '*.kmod.c' -delete 2>/dev/null || true; \
- find "$(OUTPUT_DIR)" -maxdepth 1 -type l -name '*.lib.c' -delete 2>/dev/null || true; \
- find "$(OUTPUT_DIR)" -maxdepth 1 -type f -name '*.kmod.c' -delete 2>/dev/null || true; \
- find "$(OUTPUT_DIR)" -maxdepth 1 -type f -name '*.lib.c' -delete 2>/dev/null || true; \
+ # belt & suspenders: nuke common leftovers regardless of Kbuild's behavior
+ rm -f "$(OUTPUT_DIR)"/*.o \
+ "$(OUTPUT_DIR)"/*.ko \
+ "$(OUTPUT_DIR)"/*.mod \
+ "$(OUTPUT_DIR)"/*.mod.c \
+ "$(OUTPUT_DIR)"/modules.order \
+ "$(OUTPUT_DIR)"/Module.symvers \
+ "$(OUTPUT_DIR)"/.*.cmd 2>/dev/null || true; \
+ rm -rf "$(OUTPUT_DIR)"/.tmp_versions 2>/dev/null || true; \
+ # remove staged sources we created (symlinks or copies)
+ find "$(OUTPUT_DIR)" -maxdepth 1 -type l -name '*.kmod.c' -delete 2>/dev/null || true; \
+ find "$(OUTPUT_DIR)" -maxdepth 1 -type l -name '*.lib.c' -delete 2>/dev/null || true; \
+ find "$(OUTPUT_DIR)" -maxdepth 1 -type f -name '*.kmod.c' -delete 2>/dev/null || true; \
+ find "$(OUTPUT_DIR)" -maxdepth 1 -type f -name '*.lib.c' -delete 2>/dev/null || true; \
fi
.SUFFIXES:
.EXPORT_ALL_VARIABLES:
+.DELETE_ON_ERROR:
#--------------------------------------------------------------------------------
# files have two suffixes by convention, e.g.: X.lib.c or Y.cli.c
$(error target_lib_cli.mk: no C compiler specified)
endif
-# keep only the source directories that are in the file system
-SRCDIR_LIST := $(wildcard $(SRCDIR_LIST))
+# single source directory
+C_SOURCE_DIR ?= cc
+C_SOURCE_DIR_OK := $(wildcard $(C_SOURCE_DIR))
-ifeq ($(strip $(SRCDIR_LIST)),)
- $(warning target_lib_cli.mk: empty SRCDIR_LIST)
+ifeq ($(strip $(C_SOURCE_DIR_OK)),)
+ $(warning target_lib_cli.mk: C_SOURCE_DIR '$(C_SOURCE_DIR)' not found or empty)
endif
-# duplicate source file names in different directories will cause
-# problems with this makefile
+# RT uses header integrated C source files
+CFLAGS ?= -I $(C_SOURCE_DIR)
-C_SOURCE_LIB := $(foreach dir, $(SRCDIR_LIST), $(wildcard $(dir)/*.lib.c))
-C_SOURCE_EXEC := $(foreach dir, $(SRCDIR_LIST), $(wildcard $(dir)/*.cli.c))
+# source discovery (single dir)
+C_SOURCE_LIB := $(wildcard $(C_SOURCE_DIR)/*.lib.c)
+C_SOURCE_EXEC := $(wildcard $(C_SOURCE_DIR)/*.cli.c)
-#remove the suffix to get base name
-C_BASE_LIB= $(sort $(patsubst %.lib.c, %, $(notdir $(C_SOURCE_LIB))))
-C_BASE_EXEC= $(sort $(patsubst %.cli.c, %, $(notdir $(C_SOURCE_EXEC))))
+# remove suffix to get base name
+C_BASE_LIB := $(sort $(patsubst %.lib.c,%, $(notdir $(C_SOURCE_LIB))))
+C_BASE_EXEC := $(sort $(patsubst %.cli.c,%, $(notdir $(C_SOURCE_EXEC))))
-# two sets of object files, one for the lib, and one for the command line interface progs
-OBJECT_LIB= $(patsubst %, scratchpad/%.lib.o, $(C_BASE_LIB))
-OBJECT_EXEC= $(patsubst %, scratchpad/%.cli.o, $(C_BASE_EXEC))
+
+# two sets of object files, one for the lib, and one for the CLI programs
+OBJECT_LIB := $(patsubst %, scratchpad/%.lib.o, $(C_BASE_LIB))
+OBJECT_EXEC := $(patsubst %, scratchpad/%.cli.o, $(C_BASE_EXEC))
-include $(OBJECT_LIB:.o=.d) $(OBJECT_EXEC:.o=.d)
# executables are made from EXEC sources
-EXEC= $(patsubst %, $(EXECDIR)/%, $(C_BASE_EXEC))
+EXEC := $(patsubst %, $(EXECDIR)/%, $(C_BASE_EXEC))
-# the new C programming style gated sections in source instead of header filesheader
-INCFLAG_List := $(foreach dir, $(SRCDIR_LIST), -I $(dir))
-CFLAGS += $(INCFLAG_List)
#--------------------------------------------------------------------------------
# targets
.PHONY: information
information:
@printf "· → Unicode middle dot — visible: [%b]\n" "·"
- @echo "SRCDIR_LIST: " $(SRCDIR_LIST)
+ @echo "C_SOURCE_DIR: " $(C_SOURCE_DIR)
@echo "C_SOURCE_LIB: " $(C_SOURCE_LIB)
@echo "C_SOURCE_EXEC: " $(C_SOURCE_EXEC)
@echo "C_BASE_LIB: " $(C_BASE_LIB)
@echo "EXEC: " $(EXEC)
@echo "INCFLAG_List: " $(INCFLAG_List)
-#.PHONY: library
-#library: $(LIBFILE)
-
-NEED_LIB := $(strip $(OBJECT_LIB))
-LIB_ARG := $(if $(NEED_LIB),$(LIBFILE),) # expands to lib path only when needed
-
.PHONY: library
-library: $(if $(NEED_LIB),$(LIBFILE),.remove_lib_if_exists)
-
-.PHONY: .remove_lib_if_exists
-.remove_lib_if_exists:
- @rm -f $(LIBFILE)
-
-ifneq ($(NEED_LIB),)
-$(LIBFILE): $(OBJECT_LIB)
- @echo "ar rcs $@ $^"
- ar rcs $@ $^
-endif
-
-ifeq ($(NEED_LIB),)
-$(LIBFILE):
- @rm -f $(LIBFILE)
-endif
-
+library: $(LIBFILE)
-#$(LIBFILE): $(OBJECT_LIB) $(DEPFILE)
$(LIBFILE): $(OBJECT_LIB)
- ar rcs $(LIBFILE) $(OBJECT_LIB)
-
-
-#.PHONY: cli
-#cli: $(LIBFILE) $(DEPFILE)
-#cli: $(LIBFILE)
-# make sub_cli
-
-#.PHONY: sub_cli
-#sub_cli: $(EXEC)
+ @if [ -s "$@" ] || [ -n "$(OBJECT_LIB)" ]; then \
+ echo "ar rcs $@ $^"; \
+ ar rcs $@ $^; \
+ else \
+ rm -f "$@"; \
+ fi
#.PHONY: cli
#cli: $(LIBFILE) $(EXEC)
cli: library $(EXEC)
-
# generally better to use the project local clean scripts, but this will make it so that the make targets can be run again
.PHONY: clean
# recipes
-vpath %.c $(SRCDIR_LIST)
-scratchpad/%.o: %.c
+scratchpad/%.o: $(C_SOURCE_DIR)/%.c
$(C) $(CFLAGS) -o $@ -c $<
-#$(EXECDIR)/%: scratchpad/%.cli.o $(LIBFILE)
-# $(C) -o $@ $< $(LIBFILE) $(LINKFLAGS)
-
$(EXECDIR)/%: scratchpad/%.cli.o
$(C) -o $@ $< $(LIB_ARG) $(LINKFLAGS)