For my own projects, I regularly use GNU make and often had the same requirements likeIt should work in windows cmd as well as in a *nix shellIt should work for parallel builds and without recursionIt should support cross compilingIt should generally be configurable at the command lineIt should be reliable wrt which object files need rebuildingThis ultimately led to a lot of copy-pasting from project to project with the newest project having the newest makefile features, so I decided to finally create a project only for the build system.It consists of several included modules, I'll present the most important ones here. The first one is a relatively simple module determining the host environment and setting some variables based on it:ifeq ($(OS),Windows_NT)undefine POSIXSHELLifneq ($(strip $(filter %sh,$(basename $(realpath $(SHELL))))),)POSIXSHELL := 1endifOSVER := $(subst ],,$(lastword $(shell cmd /c ver)))_ZIMK__OSVER := $(subst ., ,$(OSVER))OSVER_MAJ := $(firstword $(_ZIMK__OSVER))OSVER_MIN := $(word 2, $(_ZIMK__OSVER))OSVER_REV := $(word 3, $(_ZIMK__OSVER))elsePOSIXSHELL := 1endififdef POSIXSHELLCMDSEP := ;PSEP := /CPF := cp -fRMF := rm -fRMFR := rm -frMDP := mkdir -pMV := mvSTAMP := touchXIF := if [ -xXTHEN := ]; thenXFI := ; fiCATIN := catCATADD :=CATOUT := >EQT := # make vim syntax highlight happyCMDQUIET := >/dev/null 2>&1CODNOERR := 2>/dev/nullCMDNOIN := </dev/nullINSTALL ?= installINSTDIR := $(INSTALL) -dinstfile = $(INSTDIR) $(2) $(CMDSEP) $(INSTALL) -m$(3) $(1) $(2)geq = $(shell if test $(1) -ge $(2); then echo 1; fi)touch = touch $(1)SYSNAME := $(shell uname 2>/dev/null)elseCMDSEP := &PSEP := \\CPF := copy /yRMF := del /f /qRMFR := -rd /s /qMDP := -mdMV := moveSTAMP := copy /y NULXIF := if existXTHEN := (XFI := )CATIN := copy /bCATADD := +CATOUT :=EQT :=CMDQUIET := >nul 2>nul & verify >nulCMDNOERR := 2>nul & verify >nulCMDNOIN := <nulINSTDIR := $(MDP)instfile = $(MDP) $(dir $(2)) $(CMDQUIET) $(CMDSEP) copy $(1) $(2) $(CMDQUIET)geq = $(shell if $(1) geq $(2) echo 1)touch = copy /b $(1) +,,SYSNAME := $(shell uname 2>nul & verify >nul)endifAfter that, I know whether I'm in cmd or a *nix shell and I have some variables and functions defined to do the right thing in $(shell ) or in recipes.A little more complicated is the module that handles configurations. It can save a given set of configuration variables to config files and use them automatically. There is support for using DEFAULT_[name] variants if the user didn't specify a value for a variable. This module is also responsible for determining the target platform and set the default output directories based on build configuration and platform:define ZIMK__UNIQ$(strip $(eval undefine __ZIMK__UNIQ__SEEN)$(foreach \    _v,$1,$(if $(filter $(_v),$(__ZIMK__UNIQ__SEEN)),,$(eval \        __ZIMK__UNIQ__SEEN += $(_v))))$(__ZIMK__UNIQ__SEEN))endefSINGLECONFVARS += prefix exec_prefix bindir sbindir libexecdir datarootdir \              sysconfdir sharedstatedir localstatedir runstatedir \              includedir docrootdir libdir localedirSINGLECONFVARS := $(call ZIMK__UNIQ,CC CXX CPP AR STRIP MOC $(SINGLECONFVARS))LISTCONFVARS := $(call ZIMK__UNIQ,CFLAGS CXXFLAGS DEFINES INCLUDES LDFLAGS \    $(LISTCONFVARS))CONFVARS := $(SINGLECONFVARS) $(LISTCONFVARS)BUILDCFGS := $(call ZIMK__UNIQ,release debug $(BUILDCFGS))NOBUILDTARGETS := $(sort clean distclean config changeconfig showconfig \    _build_config _build_changeconfig $(NOBUILDTARGETS))MAKECMDGOALS ?= all-include global.cfgundefine ZIMK__EMPTYZIMK__EMPTY :=ZIMK__TAB := $(ZIMK__EMPTY) $(ZIMK__EMPTY)#default configDEFAULT_BUILDCFG ?= releaseBUILDCFG ?= $(DEFAULT_BUILDCFG)BUILDCFG := $(strip $(BUILDCFG))ZIMK__BUILDCFG := $(filter $(BUILDCFG),$(BUILDCFGS))ifndef ZIMK__BUILDCFG$(error Unknown BUILDCFG $(BUILDCFG))endifZIMK__DOUBLECONFVARS := $(filter $(SINGLECONFVARS),$(LISTCONFVARS))ifdef ZIMK__DOUBLECONFVARS$(error variables can't be in both SINGLECONFVARS and LISTCONFVARS: $(ZIMK__DOUBLECONFVARS))#' make vim syntax highlight happyendifUSERCONFIG:=$(BUILDCFG).cfgZIMK__CFGCACHE:=.cache_$(BUILDCFG).cfgdefine ZIMK__WRITECACHELINE$(ZIMK__TAB)$$(VR)echo $$(EQT)C_$(_cv) := $$(strip $($(_cv)))$$(EQT) >>$$(ZIMK__CFGCACHE)endefdefine ZIMK__WRITECACHE$$(ZIMK__CFGCACHE):    $$(VR)echo $$(EQT)# generated file, do not edit!$$(EQT) >$$(ZIMK__CFGCACHE)$(foreach _cv,$(CONFVARS),$(if $(strip $($(_cv))),$(ZIMK__WRITECACHELINE),))endefdefine ZIMK__WRITECFGLINE$(ZIMK__TAB)$$(VR)echo $$(EQT)$(_cv) ?= $$(strip $($(_cv)))$$(EQT) >>$$(USERCONFIG)endefdefine ZIMK__WRITECFG$(ZIMK__CFGTARGET): $$(USERCONFIG)    $$(VCFG)    $$(VR)echo $$(EQT)# generated file, do not edit!$$(EQT) >$$(USERCONFIG)$(foreach _cv,$(CONFVARS),$(if $(strip $($(_cv))),$(ZIMK__WRITECFGLINE),))endefdefine ZIMK__WRITECFGTAGundefine ZIMK__CFGTAG$$(foreach _cv,$$(CONFVARS), \    $$(if $$($$(_cv)),$$(eval ZIMK__CFGTAG += $$(_cv)=$$$$(strip $$$$($$(_cv)))),))ifdef ZIMK__CFGTAGZIMK__CFGTAG := $$(ZIMK__PRBOLD)[$$(ZIMK__PRRED)$$(BUILDCFG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD): $$(ZIMK__PRYELLOW)$$(ZIMK__CFGTAG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD)]$$(ZIMK__PRNORM)elseZIMK__CFGTAG := $$(ZIMK__PRBOLD)[$$(ZIMK__PRRED)$$(BUILDCFG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD)]$$(ZIMK__PRNORM)endifZIMK__CFGMSG :=ZIMK__CFGMSG +=ZIMK__CFGMSG +=ZIMK__CFGMSG += $$(ZIMK__PRBOLD)$$(ZIMK__PRYELLOW)[CFG]$$(ZIMK__PRNORM)  $$(ZIMK__CFGTAG)$$(info $$(ZIMK__CFGMSG))endefifndef MAKE_RESTARTSifneq ($(filter config,$(MAKECMDGOALS)),)$(eval $(ZIMK__WRITECFGTAG))endifendif# save userconfigZIMK__CFGTARGET := _build_config$(eval $(ZIMK__WRITECFG))-include $(USERCONFIG)ZIMK__CFGTARGET := _build_changeconfig$(eval $(ZIMK__WRITECFG))# save / compare config cacheifneq ($(filter-out $(NOBUILDTARGETS),$(MAKECMDGOALS)),)$(eval $(ZIMK__WRITECACHE))-include $(ZIMK__CFGCACHE)ifneq ($(foreach _cv,$(CONFVARS),$(_cv):$(strip $(C_$(_cv)))),$(foreach _cv,$(CONFVARS),$(_cv):$(strip $($(_cv))))).PHONY: $(ZIMK__CFGCACHE)endifendifconfig: global.cfg _build_config    $(VCFG)    $(VR)echo $(EQT)# generated file, do not edit!$(EQT) >$<    $(VR)echo $(EQT)BUILDCFG ?= $(BUILDCFG)$(EQT) >>$<changeconfig: global.cfg _build_changeconfig    $(VCFG)    $(VR)echo $(EQT)# generated file, do not edit!$(EQT) >$<    $(VR)echo $(EQT)BUILDCFG ?= $(BUILDCFG)$(EQT) >>$<global.cfg: ;$(USERCONFIG): ;ifndef MAKE_RESTARTSifneq ($(filter-out $(filter-out changeconfig,$(NOBUILDTARGETS)),$(MAKECMDGOALS)),)$(eval $(ZIMK__WRITECFGTAG))endifendifDEFAULT_CC ?= ccDEFAULT_CXX ?= c++DEFAULT_CPP ?= cppDEFAULT_AR ?= arDEFAULT_STRIP ?= stripDEFAULT_MOC ?= mocDEFAULT_CFLAGS ?= -std=c11 -Wall -Wextra -Wshadow -pedanticDEFAULT_CXXFLAGS ?= -std=c++11 -Wall -Wextra -pedanticDEFAULT_LDFLAGS ?= -L$(LIBDIR)PLATFORM_win32_CFLAGS ?= -Wno-pedantic-ms-formatPLATFORM_win32_CXXFLAGS ?= -Wno-pedantic-ms-formatPLATFORM_win32_LDFLAGS ?= -static-libgcc -static-libstdc++BUILD_debug_CFLAGS ?= -g3 -O0BUILD_debug_CXXFLAGS ?= -g3 -O0BUILD_debug_DEFINES ?= -DDEBUGBUILD_release_CFLAGS ?= -g0 -O2 -ffunction-sections -fdata-sectionsBUILD_release_CXXFLAGS ?= -g0 -O2 -ffunction-sections -fdata-sectionsBUILD_release_LDFLAGS ?= -O2 -Wl,--gc-sectionsifdef POSIXSHELLprefix ?= /usr/localexec_prefix ?= $(prefix)bindir ?= $(exec_prefix)/binsbindir ?= $(exec_prefix)/sbinlibexecdir ?= $(exec_prefix)/libexecdatarootdir ?= $(prefix)/sharesysconfdir ?= $(prefix)/etcsharedstatedir ?= $(prefix)/comlocalstatedir ?= $(prefix)/varrunstatedir ?= $(localstatedir)/runincludedir ?= $(prefix)/includedocrootdir ?= $(datarootdir)/doclibdir ?= $(exec_prefix)/liblocaledir ?= $(datarootdir)/localeelseDESTDIR ?= distprefix ?= $(PSEP).exec_prefix ?= $(prefix)bindir ?= $(exec_prefix)sbindir ?= $(exec_prefix)libexecdir ?= $(exec_prefix)datarootdir ?= $(prefix)sysconfdir ?= $(prefix)sharedstatedir ?= $(prefix)localstatedir ?= $(prefix)runstatedir ?= $(localstatedir)includedir ?= $(prefix)docrootdir ?= $(datarootdir)libdir ?= $(exec_prefix)localedir ?= $(datarootdir)endifdefine ZIMK__UPDATESINGLECFGVARSifeq ($$(strip $$(origin $(_cv))$$($(_cv))),command line)override undefine $(_cv)endif$(_cv) := $$(if $$($(_cv)),$$($(_cv)),$$(DEFAULT_$(_cv)))$(_cv) := $$(if $$($(_cv)),$$($(_cv)),$$(PLATFORM_$(PLATFORM)_$(_cv)))$(_cv) := $$(if $$($(_cv)),$$($(_cv)),$$(BUILD_$(BUILDCFG)_$(_cv)))endef$(foreach _cv,CC,$(eval $(ZIMK__UPDATESINGLECFGVARS)))ZIMK__DEFDEFINES:= $(shell $(CROSS_COMPILE)$(CC) -dM -E - $(CMDNOIN))ifeq ($(filter _WIN32,$(ZIMK__DEFDEFINES)),)PLATFORM:= posixEXE:=elsePLATFORM:= win32EXE:=.exeendifTARGETARCH:= $(strip $(shell $(CROSS_COMPILE)$(CC) -dumpmachine))ifeq ($(TARGETARCH),)TARGETARCH:= unknownendifOBJBASEDIR ?= objBINBASEDIR ?= binLIBBASEDIR ?= libTESTBASEDIR ?= testOBJDIR ?= $(OBJBASEDIR)$(PSEP)$(TARGETARCH)$(PSEP)$(BUILDCFG)BINDIR ?= $(BINBASEDIR)$(PSEP)$(TARGETARCH)$(PSEP)$(BUILDCFG)LIBDIR ?= $(LIBBASEDIR)$(PSEP)$(TARGETARCH)$(PSEP)$(BUILDCFG)TESTDIR ?= $(TESTBASEDIR)$(PSEP)$(TARGETARCH)$(PSEP)$(BUILDCFG)$(foreach _cv,$(SINGLECONFVARS),$(eval $(ZIMK__UPDATESINGLECFGVARS)))define ZIMK__UPDATELISTCFGVARSifeq ($$(strip $$(origin $(_cv))$$($(_cv))),command line)override undefine $(_cv)endif$(_cv) := $$(if $$($(_cv)),$$($(_cv)),$$(DEFAULT_$(_cv)))$(_cv) := $$(strip $$(BUILD_$(BUILDCFG)_$(_cv)) $$($(_cv)))$(_cv) := $$(strip $$(PLATFORM_$(PLATFORM)_$(_cv)) $$($(_cv)))endef$(foreach _cv,$(LISTCONFVARS),$(eval $(ZIMK__UPDATELISTCFGVARS)))ifneq ($(filter showconfig,$(MAKECMDGOALS)),)$(foreach _cv,BUILDCFG PLATFORM TARGETARCH $(CONFVARS),$(info $(_cv) = $($(_cv))))endifCLEAN += $(ZIMK__CFGCACHE)showconfig:    @:.PHONY: config changeconfig _build_config _build_changeconfig _cfg_message showconfigAnother module provides some helper functions. The most interesting of them is zinc. This is my replacement for include, it operates on relative paths and automatically sets a variable to the path where the included file is found, so I can use relative paths there as well:ZIMK__DIR :=ZIMK__PDIRS :=ZIMK__MK :=ZIMK__PMKS :=define __F_ZINCZIMK__PDIRS := $$(ZIMK__DIR) $$(ZIMK__PDIRS)ZIMK__PMKS := $$(ZIMK__MK) $$(ZIMK__PMKS)ZIMK__DIR := $$(ZIMK__DIR)$$(strip $$(subst /,$$(PSEP),$$(dir $(1))))ZIMK__MK := $$(ZIMK__DIR)$$(notdir $(1))include $$(ZIMK__MK)ZIMK__MK := $$(firstword $$(ZIMK__PMKS))ZIMK__DIR := $$(firstword $$(ZIMK__PDIRS))ZIMK__PMKS := $$(wordlist 2, $$(words $$(ZIMK__PMKS)), $$(ZIMK__PMKS))ZIMK__PDIRS := $$(wordlist 2, $$(words $$(ZIMK__PDIRS)), $$(ZIMK__PDIRS))endefdefine zinc$(eval $(__F_ZINC))endefdefine __F_BINRULES_T := $$(strip $(1))$$(eval $$(BINRULES))endefdefine binrules$(eval $(__F_BINRULES))endefdefine __F_LIBRULES_T := $$(strip $(1))$$(eval $$(LIBRULES))endefdefine librules$(eval $(__F_LIBRULES))endeftoupper = $(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,$(subst \      f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,$(subst \      k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,$(subst \      p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,$(subst \      u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,$(subst \      z,Z,$1))))))))))))))))))))))))))tolower = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst \      F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst \      K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst \      P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst \      U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst \      Z,z,$1))))))))))))))))))))))))))Then the heart of the whole thing are the modules that generate the required rules based on variables set per build target, here's the part that's common between executable and library targets:PREPROC_MOC_suffix := mocPREPROC_MOC_intype := hPREPROC_MOC_outtype := cppPREPROC_MOC_preproc := $(MOC)define OBJRULES$(_T)_MAKEFILES ?= $$(ZIMK__MK)$(_T)_SRCDIR ?= $$(patsubst %$$(PSEP),%,$$(ZIMK__DIR))$(_T)_SRCDIR := $$(strip $$($(_T)_SRCDIR))$(_T)_OBJDIR ?= $$(OBJDIR)$$(PSEP)$$($(_T)_SRCDIR)$(_T)_OBJDIR := $$(patsubst %$$(PSEP),%,$$($(_T)_OBJDIR))ifeq ($$($(_T)_SRCDIR),)$(_T)_SRCDIR := .$$(PSEP)endif$(_T)_SOURCES := $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix .c,$$($(_T)_MODULES)))$(_T)_OBJS := $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix .o,$$($(_T)_MODULES)))$(_T)_SOBJS := $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _s.o,$$($(_T)_MODULES)))ifneq ($$(strip $$($(_T)_CXXMODULES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix .cpp,$$($(_T)_CXXMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix .o,$$($(_T)_CXXMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _s.o,$$($(_T)_CXXMODULES)))endififneq ($$(strip $$($(_T)_ASMMODULES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix .S,$$($(_T)_ASMMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix .o,$$($(_T)_ASMMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _s.o,$$($(_T)_ASMMODULES)))endififneq ($$(strip $$($(_T)_PLATFORMMODULES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).c,$$($(_T)_PLATFORMMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).o,$$($(_T)_PLATFORMMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_s.o,$$($(_T)_PLATFORMMODULES)))endififneq ($$(strip $$($(_T)_PLATFORMCXXMODULES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).cpp,$$($(_T)_PLATFORMCXXMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).o,$$($(_T)_PLATFORMCXXMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_s.o,$$($(_T)_PLATFORMCXXMODULES)))endififneq ($$(strip $$($(_T)_PLATFORMASMMODULES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).S,$$($(_T)_PLATFORMASMMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM).o,$$($(_T)_PLATFORMASMMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_s.o,$$($(_T)_PLATFORMASMMODULES)))endififeq ($$(PLATFORM),win32)ifneq ($$(strip $$($(_T)_win32_RES)),)$(_T)_SOURCES += $$(addprefix $$($(_T)_SRCDIR)$$(PSEP), \    $$(addsuffix .rc,$$($(_T)_win32_RES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix .o,$$($(_T)_win32_RES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix .o,$$($(_T)_win32_RES)))endifendififneq ($$(strip $$($(_T)_PREPROC)),)$(_T)_PPSRCDIR ?= $$($(_T)_SRCDIR)$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PREPROC_$$($(_T)_PREPROC)_suffix).o, \    $$($(_T)_PREPROCMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PREPROC_$$($(_T)_PREPROC)_suffix)_s.o, \    $$($(_T)_PREPROCMODULES)))$(_T)_OBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_$$(PREPROC_$$($(_T)_PREPROC)_suffix).o, \    $$($(_T)_PLATFORMPREPROCMODULES)))$(_T)_SOBJS += $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_$$(PREPROC_$$($(_T)_PREPROC)_suffix)_s.o, \    $$($(_T)_PLATFORMPREPROCMODULES)))$(_T)_PPSOURCES := $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PREPROC_$$($(_T)_PREPROC)_suffix).c, \    $$($(_T)_PREPROCMODULES))) \    $$(addprefix $$($(_T)_OBJDIR)$$(PSEP), \    $$(addsuffix _$$(PLATFORM)_$$(PREPROC_$$($(_T)_PREPROC)_suffix).c, \    $$($(_T)_PLATFORMPREPROCMODULES)))CLEAN += $$($(_T)_PPSOURCES)$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).$$(PREPROC_$$($(_T)_PREPROC)_outtype): \            $$($(_T)_PPSRCDIR)$$(PSEP)%.$$(PREPROC_$$($(_T)_PREPROC)_intype) \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) \    | $$(_$(_T)_DIRS) $$($(_T)_DEPS)    $$(VGEN)    $$(VR)$$(PREPROC_$$($(_T)_PREPROC)_preproc) \            $$($(_T)_PREPROCFLAGS) $$< >$$@endifCLEAN += $$($(_T)_OBJS:.o=.d) $$($(_T)_OBJS)OUTFILES := $$($(_T)_OBJS)$(DIRRULES)%.o: %.c$$($(_T)_OBJDIR)$$(PSEP)%.d: $$($(_T)_SRCDIR)$$(PSEP)%.c \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VDEP)    $$(VR)$$(CROSS_COMPILE)$$(CC) -MM -MT$$@ $$(@:.d=.o) -MF$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%.d: $$($(_T)_SRCDIR)$$(PSEP)%.cpp \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VDEP)    $$(VR)$$(CROSS_COMPILE)$$(CXX) -MM -MT$$@ $$(@:.d=.o) -MF$$@ \            $$($(_T)_$$(PLATFORM)_CXXFLAGS) $$($(_T)_CXXFLAGS) $$(CXXFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%.d: $$($(_T)_SRCDIR)$$(PSEP)%.S \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VDEP)    $$(VR)$$(CROSS_COMPILE)$$(CC) -MM -MT$$@ $$(@:.d=.o) -MF$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%.d: $$($(_T)_SRCDIR)$$(PSEP)%.rc \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VDEP)    $$(VR)$$(CROSS_COMPILE)$$(CPP) -MM -MT$$@ $$(@:.d=.o) -MF$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<ifneq ($(filter-out $(NOBUILDTARGETS),$(MAKECMDGOALS)),)-include $$($(_T)_OBJS:.o=.d)endififeq ($$(PLATFORM),win32)$$($(_T)_OBJDIR)$$(PSEP)%.o: $$($(_T)_SRCDIR)$$(PSEP)%.rc \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VRES)    $$(VR)$$(CROSS_COMPILE)windres $$< $$@endif$$($(_T)_OBJDIR)$$(PSEP)%.o: $$($(_T)_SRCDIR)$$(PSEP)%.c \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_STATIC) $$($(_T)_CFLAGS_STATIC) \            $$(CFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_s.o: $$($(_T)_SRCDIR)$$(PSEP)%.c \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_SHARED) $$($(_T)_CFLAGS_SHARED) \            $$(CFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%.o: $$($(_T)_SRCDIR)$$(PSEP)%.cpp \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCXX)    $$(VR)$$(CROSS_COMPILE)$$(CXX) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CXXFLAGS_STATIC) $$($(_T)_CXXFLAGS_STATIC) \            $$(CXXFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CXXFLAGS) $$($(_T)_CXXFLAGS) $$(CXXFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_s.o: $$($(_T)_SRCDIR)$$(PSEP)%.cpp \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCXX)    $$(VR)$$(CROSS_COMPILE)$$(CXX) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CXXFLAGS_SHARED) $$($(_T)_CXXFLAGS_SHARED) \            $$(CXXFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CXXFLAGS) $$($(_T)_CXXFLAGS) $$(CXXFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%.o: $$($(_T)_SRCDIR)$$(PSEP)%.S \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCAS)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_STATIC) $$($(_T)_CFLAGS_STATIC) \            $$(CFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_s.o: $$($(_T)_SRCDIR)$$(PSEP)%.S \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCAS)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_SHARED) $$($(_T)_CFLAGS_SHARED) \            $$(CFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<ifneq ($$(strip $$($(_T)_PREPROC)),)$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).c \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_STATIC) $$($(_T)_CFLAGS_STATIC) \            $$(CFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix)_s.o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).c \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_SHARED) $$($(_T)_CFLAGS_SHARED) \            $$(CFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).cpp \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCXX)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CXXFLAGS_STATIC) $$($(_T)_CXXFLAGS_STATIC) \            $$(CXXFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CXXFLAGS) $$($(_T)_CXXFLAGS) $$(CXXFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix)_s.o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).cpp \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCXX)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CXXFLAGS_SHARED) $$($(_T)_CXXFLAGS_SHARED) \            $$(CXXFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CXXFLAGS) $$($(_T)_CXXFLAGS) $$(CXXFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).S \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_STATIC) $$($(_T)_CFLAGS_STATIC) \            $$(CFLAGS_STATIC) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<$$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix)_s.o: \            $$($(_T)_OBJDIR)$$(PSEP)%_$$(PREPROC_$$($(_T)_PREPROC)_suffix).S \    $$($(_T)_MAKEFILES) $$(ZIMK__CFGCACHE) | $$(_$(_T)_DIRS)    $$(VCC)    $$(VR)$$(CROSS_COMPILE)$$(CC) -c -o$$@ \            $$($(_T)_$$(PLATFORM)_CFLAGS_SHARED) $$($(_T)_CFLAGS_SHARED) \            $$(CFLAGS_SHARED) \            $$($(_T)_$$(PLATFORM)_CFLAGS) $$($(_T)_CFLAGS) $$(CFLAGS) \            $$($(_T)_$$(PLATFORM)_DEFINES) $$($(_T)_DEFINES) $$(DEFINES) \            $$($(_T)_$$(PLATFORM)_INCLUDES) $$($(_T)_INCLUDES) \            $$(INCLUDES) $$<endifendef.SECONDARY:I know this is a lot of code, but it makes the project-specific makefiles (in my opinion) very concise and easy to understand. A single-file snake game for example uses this Makefile:include zimk/zimk.mkminsnake_MODULES:= minsnakeminsnake_win32_STATICLIBS:= pdcursesminsnake_posix_LIBS:= ncurses$(call binrules,minsnake)For a more complex setup, I have for example this:[...]include zimk/zimk.mkINCLUDES += -I.$(PSEP)include$(call zinc, src/lib/core/core.mk)[...]and in core.mk:pocascore_MODULES:= cmdline string stringbuilder list hashtable plugin mtwaitqueue event eventloop textcolorpocascore_PLATFORMMODULES := textcolor dso plugin eventloop file processhostpocascore_DEFINES:= -DBUILDING_POCAS_COREpocascore_posix_LDFLAGS:= -pthreadpocascore_win32_STATICLIBS:= pthreadpocascore_posix_LIBS:= dlpocascore_V_MAJ:= 0pocascore_V_MIN:= 0pocascore_V_REV:= 1$(call librules, pocascore)This creates a libpocascore.so.0 or a pocascore-0.dll, depending on the target system, and optionally also static libs.Now, do you think this is a decent approach? Am I maybe missing something important that's not doable with this design?Here's a link to the whole project for reference.UPDATE: I just found one shortcoming (or: BUG, depending on your point of view) myself by accident. Right now, the $(eval )-generated rules only work correctly if every target has its own source directory exclusively. Although this is a good rule for project layout in general, it might be broken for really small targets, like test cases. I'm not sure yet whether I'll try to update the code to support this. It would be a major change because it would mean not to use pattern rules at all.
Using GNU make for a build system without hand-written rules
make