For my own projects, I regularly use GNU make and often had the same requirements like
- It should work in windows
cmd
as well as in a *nix shell - It should work for parallel builds and without recursion
- It should support cross compiling
- It should generally be configurable at the command line
- It should be reliable wrt which object files need rebuilding
This 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 POSIXSHELL
ifneq ($(strip $(filter %sh,$(basename $(realpath $(SHELL))))),)
POSIXSHELL := 1
endif
OSVER := $(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))
else
POSIXSHELL := 1
endif
ifdef POSIXSHELL
CMDSEP := ;
PSEP := /
CPF := cp -f
RMF := rm -f
RMFR := rm -fr
MDP := mkdir -p
MV := mv
STAMP := touch
XIF := if [ -x
XTHEN := ]; then
XFI := ; fi
CATIN := cat
CATADD :=
CATOUT := >
EQT := "
#" make vim syntax highlight happy
CMDQUIET := >/dev/null 2>&1
CODNOERR := 2>/dev/null
CMDNOIN := </dev/null
INSTALL ?= install
INSTDIR := $(INSTALL) -d
instfile = $(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)
else
CMDSEP := &
PSEP := \\
CPF := copy /y
RMF := del /f /q
RMFR := -rd /s /q
MDP := -md
MV := move
STAMP := copy /y NUL
XIF := if exist
XTHEN := (
XFI := )
CATIN := copy /b
CATADD := +
CATOUT :=
EQT :=
CMDQUIET := >nul 2>nul & verify >nul
CMDNOERR := 2>nul & verify >nul
CMDNOIN := <nul
INSTDIR := $(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)
endif
After 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))
endef
SINGLECONFVARS += prefix exec_prefix bindir sbindir libexecdir datarootdir \
sysconfdir sharedstatedir localstatedir runstatedir \
includedir docrootdir libdir localedir
SINGLECONFVARS := $(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.cfg
undefine ZIMK__EMPTY
ZIMK__EMPTY :=
ZIMK__TAB := $(ZIMK__EMPTY) $(ZIMK__EMPTY)
#default config
DEFAULT_BUILDCFG ?= release
BUILDCFG ?= $(DEFAULT_BUILDCFG)
BUILDCFG := $(strip $(BUILDCFG))
ZIMK__BUILDCFG := $(filter $(BUILDCFG),$(BUILDCFGS))
ifndef ZIMK__BUILDCFG
$(error Unknown BUILDCFG $(BUILDCFG))
endif
ZIMK__DOUBLECONFVARS := $(filter $(SINGLECONFVARS),$(LISTCONFVARS))
ifdef ZIMK__DOUBLECONFVARS
$(error variables can't be in both SINGLECONFVARS and LISTCONFVARS: $(ZIMK__DOUBLECONFVARS))
#' make vim syntax highlight happy
endif
USERCONFIG:=$(BUILDCFG).cfg
ZIMK__CFGCACHE:=.cache_$(BUILDCFG).cfg
define ZIMK__WRITECACHELINE
$(ZIMK__TAB)$$(VR)echo $$(EQT)C_$(_cv) := $$(strip $($(_cv)))$$(EQT) >>$$(ZIMK__CFGCACHE)
endef
define ZIMK__WRITECACHE
$$(ZIMK__CFGCACHE):
$$(VR)echo $$(EQT)# generated file, do not edit!$$(EQT) >$$(ZIMK__CFGCACHE)$(foreach _cv,$(CONFVARS),$(if $(strip $($(_cv))),$(ZIMK__WRITECACHELINE),))
endef
define ZIMK__WRITECFGLINE
$(ZIMK__TAB)$$(VR)echo $$(EQT)$(_cv) ?= $$(strip $($(_cv)))$$(EQT) >>$$(USERCONFIG)
endef
define ZIMK__WRITECFG
$(ZIMK__CFGTARGET): $$(USERCONFIG)
$$(VCFG)
$$(VR)echo $$(EQT)# generated file, do not edit!$$(EQT) >$$(USERCONFIG)$(foreach _cv,$(CONFVARS),$(if $(strip $($(_cv))),$(ZIMK__WRITECFGLINE),))
endef
define ZIMK__WRITECFGTAG
undefine ZIMK__CFGTAG
$$(foreach _cv,$$(CONFVARS), \
$$(if $$($$(_cv)),$$(eval ZIMK__CFGTAG += $$(_cv)=$$$$(strip $$$$($$(_cv)))),))
ifdef ZIMK__CFGTAG
ZIMK__CFGTAG := $$(ZIMK__PRBOLD)[$$(ZIMK__PRRED)$$(BUILDCFG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD): $$(ZIMK__PRYELLOW)$$(ZIMK__CFGTAG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD)]$$(ZIMK__PRNORM)
else
ZIMK__CFGTAG := $$(ZIMK__PRBOLD)[$$(ZIMK__PRRED)$$(BUILDCFG)$$(ZIMK__PRNORM)$$(ZIMK__PRBOLD)]$$(ZIMK__PRNORM)
endif
ZIMK__CFGMSG :=
ZIMK__CFGMSG +=
ZIMK__CFGMSG +=
ZIMK__CFGMSG += $$(ZIMK__PRBOLD)$$(ZIMK__PRYELLOW)[CFG]$$(ZIMK__PRNORM) $$(ZIMK__CFGTAG)
$$(info $$(ZIMK__CFGMSG))
endef
ifndef MAKE_RESTARTS
ifneq ($(filter config,$(MAKECMDGOALS)),)
$(eval $(ZIMK__WRITECFGTAG))
endif
endif
# save userconfig
ZIMK__CFGTARGET := _build_config
$(eval $(ZIMK__WRITECFG))
-include $(USERCONFIG)
ZIMK__CFGTARGET := _build_changeconfig
$(eval $(ZIMK__WRITECFG))
# save / compare config cache
ifneq ($(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)
endif
endif
config: 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_RESTARTS
ifneq ($(filter-out $(filter-out changeconfig,$(NOBUILDTARGETS)),$(MAKECMDGOALS)),)
$(eval $(ZIMK__WRITECFGTAG))
endif
endif
DEFAULT_CC ?= cc
DEFAULT_CXX ?= c++
DEFAULT_CPP ?= cpp
DEFAULT_AR ?= ar
DEFAULT_STRIP ?= strip
DEFAULT_MOC ?= moc
DEFAULT_CFLAGS ?= -std=c11 -Wall -Wextra -Wshadow -pedantic
DEFAULT_CXXFLAGS ?= -std=c++11 -Wall -Wextra -pedantic
DEFAULT_LDFLAGS ?= -L$(LIBDIR)
PLATFORM_win32_CFLAGS ?= -Wno-pedantic-ms-format
PLATFORM_win32_CXXFLAGS ?= -Wno-pedantic-ms-format
PLATFORM_win32_LDFLAGS ?= -static-libgcc -static-libstdc++
BUILD_debug_CFLAGS ?= -g3 -O0
BUILD_debug_CXXFLAGS ?= -g3 -O0
BUILD_debug_DEFINES ?= -DDEBUG
BUILD_release_CFLAGS ?= -g0 -O2 -ffunction-sections -fdata-sections
BUILD_release_CXXFLAGS ?= -g0 -O2 -ffunction-sections -fdata-sections
BUILD_release_LDFLAGS ?= -O2 -Wl,--gc-sections
ifdef POSIXSHELL
prefix ?= /usr/local
exec_prefix ?= $(prefix)
bindir ?= $(exec_prefix)/bin
sbindir ?= $(exec_prefix)/sbin
libexecdir ?= $(exec_prefix)/libexec
datarootdir ?= $(prefix)/share
sysconfdir ?= $(prefix)/etc
sharedstatedir ?= $(prefix)/com
localstatedir ?= $(prefix)/var
runstatedir ?= $(localstatedir)/run
includedir ?= $(prefix)/include
docrootdir ?= $(datarootdir)/doc
libdir ?= $(exec_prefix)/lib
localedir ?= $(datarootdir)/locale
else
DESTDIR ?= dist
prefix ?= $(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)
endif
define ZIMK__UPDATESINGLECFGVARS
ifeq ($$(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:= posix
EXE:=
else
PLATFORM:= win32
EXE:=.exe
endif
TARGETARCH:= $(strip $(shell $(CROSS_COMPILE)$(CC) -dumpmachine))
ifeq ($(TARGETARCH),)
TARGETARCH:= unknown
endif
OBJBASEDIR ?= obj
BINBASEDIR ?= bin
LIBBASEDIR ?= lib
TESTBASEDIR ?= test
OBJDIR ?= $(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__UPDATELISTCFGVARS
ifeq ($$(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))))
endif
CLEAN += $(ZIMK__CFGCACHE)
showconfig:
@:
.PHONY: config changeconfig _build_config _build_changeconfig _cfg_message showconfig
Another 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_ZINC
ZIMK__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))
endef
define zinc
$(eval $(__F_ZINC))
endef
define __F_BINRULES
_T := $$(strip $(1))
$$(eval $$(BINRULES))
endef
define binrules
$(eval $(__F_BINRULES))
endef
define __F_LIBRULES
_T := $$(strip $(1))
$$(eval $$(LIBRULES))
endef
define librules
$(eval $(__F_LIBRULES))
endef
toupper = $(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 := moc
PREPROC_MOC_intype := h
PREPROC_MOC_outtype := cpp
PREPROC_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)))
endif
ifneq ($$(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)))
endif
ifneq ($$(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)))
endif
ifneq ($$(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)))
endif
ifneq ($$(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)))
endif
ifeq ($$(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)))
endif
endif
ifneq ($$(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) $$< >$$@
endif
CLEAN += $$($(_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)
endif
ifeq ($$(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) $$<
endif
endef
.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.mk
minsnake_MODULES:= minsnake
minsnake_win32_STATICLIBS:= pdcurses
minsnake_posix_LIBS:= ncurses
$(call binrules,minsnake)
For a more complex setup, I have for example this:
[...]
include zimk/zimk.mk
INCLUDES += -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 textcolor
pocascore_PLATFORMMODULES := textcolor dso plugin eventloop file processhost
pocascore_DEFINES:= -DBUILDING_POCAS_CORE
pocascore_posix_LDFLAGS:= -pthread
pocascore_win32_STATICLIBS:= pthread
pocascore_posix_LIBS:= dl
pocascore_V_MAJ:= 0
pocascore_V_MIN:= 0
pocascore_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.