make hierarchy
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Wed, 11 Nov 2026 06:53:41 +0000 (06:53 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Wed, 11 Nov 2026 06:53:41 +0000 (06:53 +0000)
developer/authored/Man_In_Grey_gate.make/Man_In_Grey_gate.mk [new file with mode: 0644]
developer/tool/build_component/make
developer/tool/build_component/makefile
shared/tool/makefile/Harmony.mk

diff --git a/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_gate.mk b/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_gate.mk
new file mode 100644 (file)
index 0000000..671ce9d
--- /dev/null
@@ -0,0 +1,23 @@
+# Man_In_Grey_gate.mk
+#
+# The settings for building a gate on Man_In_Grey's general gate. Harmony.mk
+# reads this file last when it builds this namespace, and stages it with
+# libMan_In_Grey.a and Gate.lib.c, so promotion puts all three in
+# consumer/Man_In_Grey_gate/. An application builds its own gate by including
+# the promoted copy from a .mk beside its gate's .CLI.c, for example:
+#
+#   include $(REPO_HOME)/shared/project-dependency/Man_In_Grey/Man_In_Grey_gate/Man_In_Grey_gate.mk
+#
+# The directory of this file: the last makefile make has read is the one it
+# is reading now, and := takes the value before this file includes anything.
+Man_In_Grey_gate_dp := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
+# A gate runs setuid, so it is built hardened.
+CFLAGS   += -O2 -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2
+LN_FLAGS += -Wl,-z,relro -Wl,-z,now
+
+# Where a gate built in another project finds Gate.lib.c and libMan_In_Grey.a.
+# In Man_In_Grey's own build this is the source directory, and Harmony.mk
+# finds both without it.
+CFLAGS   += -I $(Man_In_Grey_gate_dp)
+LN_FLAGS += -L$(Man_In_Grey_gate_dp) -lMan_In_Grey
index 30fb82d..82df53f 100755 (executable)
@@ -12,4 +12,6 @@ export KMOD_SOURCE_DIR="authored/$NAMESPACE_DIR"
 export LIBRARY_DIR="scratchpad/made/$NAMESPACE"
 export MACHINE_DIR="scratchpad/made/$NAMESPACE"
 
-/bin/make -f tool/build_component/makefile "$CMD"
+# Harmony.mk is the makefile run; it includes tool/build_component/makefile,
+# the project's settings, and the namespace's *.mk files.
+/bin/make -f "$REPO_HOME/shared/tool/makefile/Harmony.mk" "$CMD"
index cba5ea4..ed4ec4a 100644 (file)
@@ -1,61 +1,20 @@
 # developer/tool/build_component/makefile
-.SUFFIXES:
-.EXPORT_ALL_VARIABLES:
-
-RT_MAKEFILE_DP := $(REPO_HOME)/shared/tool/makefile
-
-# the setuid gate is built hardened
-ifeq ($(NAMESPACE),Man_In_Grey_gate)
-  CFLAGS_PROJECT_LOCAL  := -O2 -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2
-  LN_FLAGS_PROJECT_LOCAL := -Wl,-z,relro -Wl,-z,now
-endif
-
-.PHONY: usage 
-usage:
-       @printf "Usage: make [usage|version|information|all|lib|CLI|kmod|clean]\n"
-
-.PHONY: version 
-version:
-       @printf "local ----------------------------------------\n"
-       @echo tool/makefile version 2.0
-       @printf "Harmony.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk version
-       @printf "target_kernel-module.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk version
-
-.PHONY: information
-information:
-       @printf "local ----------------------------------------\n"
-       -@echo CURDIR='$(CURDIR)'
-       @echo REPO_HOME="$(REPO_HOME)"
-       @echo NAMESPACE="$(NAMESPACE)"
-       @echo C_SOURCE_DIR="$(C_SOURCE_DIR)"
-       @echo KMOD_BUILD_DIR="/lib/modules/$(shell uname -r)/build"
-       @echo CURDIR="$(CURDIR)"
-       @printf "Harmony.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk information
-       @printf "target_kernel-module.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk information
-
-.PHONY: all
-all: library CLI kmod
-
-.PHONY: library lib
-library lib:
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk library
-
-.PHONY: CLI
-CLI:
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk CLI
-
-.PHONY: kmod
-kmod:
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk kmod
-
-.PHONY: clean
-clean:
-       @printf "local ----------------------------------------\n"
-       @printf "Harmony.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk clean
-       @printf "target_kernel-module.mk ----------------------------------------\n"
-       @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk clean
+#
+# The project's make settings, for every namespace the project builds with
+# make. Harmony.mk includes this file after its own defaults and before the
+# namespace's *.mk files, which have the last say. This file holds settings,
+# not rules for building: Harmony.mk has those.
+#
+# Add to a default with +=, replace one with :=, for example:
+#
+#   CFLAGS   += -O2
+#   LN_FLAGS += -lm
+#
+# Settings for one namespace go in a .mk file beside its source instead, such
+# as authored/<namespace>.make/<namespace>.mk. That file is also staged and
+# promoted with the namespace's library, so a project built against the
+# library reads the same settings.
+#
+# Harmony.mk's information and clean are double-colon rules, so a recipe
+# added here as 'information::' or 'clean::' runs as well as Harmony.mk's.
+# Make runs them in the order it read them, so this file's runs first.
index 78db1a2..2f95a03 100644 (file)
@@ -1,40 +1,50 @@
 # make/Harmony.mk - build *.lib.c and *.CLI.c
 # written for the Harmony skeleton, always invoked from cwd  $REPO_HOME/<role>
-# files have two suffixes by convention, e.g.: X.lib.c or Y.CLI.c 
+# files have two suffixes by convention, e.g.: X.lib.c or Y.CLI.c
+#
+# One make reads three layers, each after the one before, so each can change
+# what the layers before it set:
+#
+#   1. Harmony's defaults, in this file
+#   2. the project's settings, tool/build_component/makefile
+#   3. the namespace's settings, any *.mk beside its source
+#
+# A layer adds to a default with += and replaces one with :=. The flags the
+# build itself depends on (dependency files, the global header, the source
+# directory) are added after the layers, so replacing a default cannot remove
+# them.
+#
+# The layers are read before the derived variables and the rules, because
+# make expands those as it reads them. A rule a layer defines is read at that
+# point too, so its targets and prerequisites cannot use the derived lists.
 
 .SUFFIXES:
 .DELETE_ON_ERROR:
+.DEFAULT_GOAL := usage
 
 #--------------------------------------------------------------------------------
-# Harmony structure
+# 1. Harmony's defaults
 
-SHELL=/bin/bash
+SHELL := /bin/bash
 
 ECHO := printf "%b\n"
 
-C_SOURCE_DIR     ?= authored
-C                ?= gcc
-CFLAGS           ?= -std=gnu11 -Wall -Wextra -Wpedantic -finput-charset=UTF-8
-CFLAGS           += -MMD -MP
-CFLAGS           += -include "$(REPO_HOME)/shared/tool/makefile/RT_global.h"
-CFLAGS           += -I $(C_SOURCE_DIR)
-CFLAGS           += $(CFLAGS_PROJECT_LOCAL)
-
-# Project administrators can override this in their local makefile
-LIBRARY_NAME     ?= $(PROJECT)
-LIBRARY_NAME     := $(subst -,_,$(LIBRARY_NAME))
+RT_MAKEFILE_DP := $(REPO_HOME)/shared/tool/makefile
 
-BUILD_DIR        ?= scratchpad/build
-OBJECT_DIR       ?= $(BUILD_DIR)/object
+# The build component supplies these on the environment, for the namespace
+# being built; ?= keeps its values, and a layer can still replace them.
+C_SOURCE_DIR     ?= authored
 LIBRARY_DIR      ?= scratchpad/made
 MACHINE_DIR      ?= scratchpad/made
 
-LIBRARY_FILE     ?= $(LIBRARY_DIR)/lib$(LIBRARY_NAME).a
+C                := gcc
+CFLAGS           := -std=gnu11 -Wall -Wextra -Wpedantic -finput-charset=UTF-8
+LN_FLAGS         := -L/lib64 -L/lib
 
-# the project library is linked only when the namespace makes one
-LN_FLAGS         ?= -L$(LIBRARY_DIR) -L/lib64 -L/lib $(if $(object_lib),-l$(LIBRARY_NAME))
-# LN_FLAGS         ?= -L$(LIBRARY_DIR) -L/lib64 -L/lib -l$(LIBRARY_NAME)
-LN_FLAGS         += $(LN_FLAGS_PROJECT_LOCAL)
+LIBRARY_NAME     := $(PROJECT)
+
+BUILD_DIR        := scratchpad/build
+OBJECT_DIR        = $(BUILD_DIR)/object
 
 KMOD_SOURCE_DIR  ?= authored
 KMOD_CCFLAGS     ?= -I $(KMOD_SOURCE_DIR)
@@ -42,9 +52,22 @@ KMOD_CCFLAGS     ?= -I $(KMOD_SOURCE_DIR)
 KMOD_CCFLAGS     += -include $(REPO_HOME)/shared/tool/makefile/RT_global.h
 KMOD_OUTPUT_DIR  ?= scratchpad/kmod
 
+#--------------------------------------------------------------------------------
+# 2. the project's settings
+
+include tool/build_component/makefile
+
+#--------------------------------------------------------------------------------
+# 3. the namespace's settings
+
+-include $(wildcard $(C_SOURCE_DIR)/*.mk)
+
 #--------------------------------------------------------------------------------
 # derived variables
 
+LIBRARY_NAME     := $(subst -,_,$(LIBRARY_NAME))
+LIBRARY_FILE     := $(LIBRARY_DIR)/lib$(LIBRARY_NAME).a
+
 # source discovery (single dir)
 c_source_lib  := $(wildcard $(C_SOURCE_DIR)/*.lib.c)
 c_source_exec := $(wildcard $(C_SOURCE_DIR)/*.CLI.c)
@@ -60,6 +83,20 @@ object_exec := $(patsubst %, $(OBJECT_DIR)/%.CLI.o, $(c_base_exec))
 # executables are made from exec_ sources
 exec_ := $(patsubst %, $(MACHINE_DIR)/%, $(c_base_exec))
 
+# A library's product is its archive, its interface and its settings: in the
+# single-file C module the .lib.c is its own header, and a .mk holds what a
+# program built against the library needs. Both are staged beside the
+# archive, so promotion hands a consumer all three. A namespace that makes no
+# library stages neither.
+interface_ := $(if $(c_source_lib),$(patsubst $(C_SOURCE_DIR)/%,$(LIBRARY_DIR)/%,$(c_source_lib) $(wildcard $(C_SOURCE_DIR)/*.mk)))
+
+# the flags the build depends on, after the layers
+CFLAGS   += -MMD -MP
+CFLAGS   += -include "$(REPO_HOME)/shared/tool/makefile/RT_global.h"
+CFLAGS   += -I $(C_SOURCE_DIR)
+# the project library is linked only when the namespace makes one
+LN_FLAGS += -L$(LIBRARY_DIR) $(if $(object_lib),-l$(LIBRARY_NAME))
+
 #--------------------------------------------------------------------------------
 # pull in dependencies
 
@@ -69,25 +106,31 @@ exec_ := $(patsubst %, $(MACHINE_DIR)/%, $(c_base_exec))
 #--------------------------------------------------------------------------------
 # targets
 
-# when no target is given make uses the first target, this one
 .PHONY: usage
 usage:
-       @echo example usage: make clean
-       @echo example usage: make library
-       @echo example usage: make CLI
-       @echo example usage: make library CLI
+       @printf "Usage: make [usage|version|information|all|library|lib|CLI|kmod|clean]\n"
 
 .PHONY: version
 version:
-       @echo makefile version 8.0
+       @echo Harmony.mk version 9.0
        if [ ! -z "$(C)" ]; then $(C) -v; fi
        /bin/make -v
+       @$(MAKE) --no-print-directory -f $(RT_MAKEFILE_DP)/target_kernel-module.mk version
 
+# information and clean are double-colon rules, so the project's settings or a
+# namespace's .mk can add a recipe of its own; each recipe runs.
 .PHONY: information
-information:
+information::
        @printf "· → Unicode middle dot - visible: [%b]\n" "·"
+       @echo "REPO_HOME: " $(REPO_HOME)
+       @echo "NAMESPACE: " $(NAMESPACE)
        @echo "C_SOURCE_DIR: " $(C_SOURCE_DIR)
        @echo "BUILD_DIR: " $(BUILD_DIR)
+       @echo "LIBRARY_DIR: " $(LIBRARY_DIR)
+       @echo "MACHINE_DIR: " $(MACHINE_DIR)
+       @echo "makefiles read: " $(MAKEFILE_LIST)
+       @echo "CFLAGS: " $(CFLAGS)
+       @echo "LN_FLAGS: " $(LN_FLAGS)
        @echo "c_source_lib: " $(c_source_lib)
        @echo "c_source_exec: " $(c_source_exec)
        @echo "c_base_lib: " $(c_base_lib)
@@ -95,40 +138,50 @@ information:
        @echo "object_lib: " $(object_lib)
        @echo "object_exec: " $(object_exec)
        @echo "exec_: " $(exec_)
+       @echo "interface_: " $(interface_)
+       @$(MAKE) --no-print-directory -f $(RT_MAKEFILE_DP)/target_kernel-module.mk information
+
+.PHONY: all
+all: library CLI kmod
 
-.PHONY: library
-library: $(LIBRARY_FILE)
+.PHONY: library lib
+library lib: $(LIBRARY_FILE) $(interface_)
 
 $(LIBRARY_FILE): $(object_lib)
-       @mkdir -p $(MACHINE_DIR)
+       @mkdir -p $(LIBRARY_DIR)
        @if [ -s "$@" ] || [ -n "$(object_lib)" ]; then \
                echo "ar rcs $@ $^"; \
                ar rcs $@ $^; \
        else \
                rm -f "$@"; \
-       fi   
+       fi
 
-#.PHONY: CLI
-#CLI: $(LIBRARY_FILE) $(exec_)
+# each staged interface file depends on its source file of the same name
+$(interface_): $(LIBRARY_DIR)/%: $(C_SOURCE_DIR)/%
+       @mkdir -p $(LIBRARY_DIR)
+       cp $< $@
 
 .PHONY: CLI
 CLI: library $(exec_)
 
+# The kernel modules are built by a make of their own, which drives Kbuild.
+# It defines targets of the same names as this file, so it is run, not
+# included.
+.PHONY: kmod
+kmod:
+       @$(MAKE) --no-print-directory -f $(RT_MAKEFILE_DP)/target_kernel-module.mk kmod
 
 # 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
-clean:
-       rm -f $(LIBRARY_FILE)
+clean::
+       rm -f $(LIBRARY_FILE) $(interface_)
        for obj in $(object_lib) $(object_exec); do rm -f $$obj $${obj%.o}.d || true; done
        for i in $(exec_); do [ -e $$i ] && rm $$i || true; done
        rm -rf $(BUILD_DIR)
+       @$(MAKE) --no-print-directory -f $(RT_MAKEFILE_DP)/target_kernel-module.mk clean
 
 
 # recipes
-#$(OBJECT_DIR)/%.o: $(C_SOURCE_DIR)/%.c
-#      @mkdir -p $(OBJECT_DIR)
-#      $(C) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
 
 $(OBJECT_DIR)/%.o: $(C_SOURCE_DIR)/%.c
        @mkdir -p $(OBJECT_DIR)