Skip to content

Instantly share code, notes, and snippets.

@duaneg
Last active May 8, 2017 16:16
Show Gist options
  • Save duaneg/86f3a122b66aa341b369 to your computer and use it in GitHub Desktop.
Save duaneg/86f3a122b66aa341b369 to your computer and use it in GitHub Desktop.
GNU Make modular makefile framework
#***********************************************************************
#
# Build system module framework
#
# Copyright (c) 2014 Process Systems Enterprise Ltd
#
# Licensed under GPL v3+
#
# This makefile provides definitions which allow use of a modular build system,
# where "module" makefiles are isolated from each other. Variables defined in a
# makefile are local to that makefile; changed values are reset and new ones
# undefined after processing each one.
#
# It requires GNU make version 3.82 to work properly. If used with 3.81 it will
# fall back to clearing variables listed in SCRATCH_VARS and doing a simple
# include of the module. No saving/restoring of state will be done. Earlier
# versions of GNU make, or other implementations, will likely not work at all.
#
# The following variables are used to control module processing:
#
# SCRATCH_VARS: a list of variables which must be undefined at the point the
# module is included. They will be undefined/cleared for each
# module, even when falling-back on restricted functionality due
# to an unsupported make version.
#
# EXPORT: a list of variables whose value should *not* be cleared/reset after
# the module is processed. Variables which are EXPORTed from more than
# one module may only be appended to.
#
# OVERRIDE: a list of variables whose value should *not* be cleared/reset after
# the module is processed. Variables listed here may change the value
# set by other modules (i.e. they are not append-only, unlike
# EXPORT).
#
# NOTE: The control variables are not treated specially. They will be local to
# each module, unless listed in EXPORT or OVERRIDE themselves. However
# adding them to EXPORT or OVERRIDE has not been tested and may or may
# not work.
#
# WARNING: Due to the way rules are evaluated any variable used within them
# must be exported!
#
# EXAMPLE USE:
#
# MODULES := foo bar
# SCRATCH_VARS := NAME SRCS CFLAGS INCS LIBS
#
# include mk/Build.mk
# include mk/Modules.mk
# $(foreach module,$(MODULES),$(eval $(call IncludeModule,$(module)/Module.mk)))
#
#***********************************************************************
# The scratch variables should not have a value yet
# Confirm that and set their initial value to empty
define ConfirmUndefined
$$(if $$(strip $$(subst undefined,,$$(origin $(1)))),$$(error Scratch variable $(1) has been defined),)
endef
$(foreach var,$(SCRATCH_VARS),$(eval $(call ConfirmUndefined,$(var))))
ifdef .FEATURES
ifeq (undefine,$(filter undefine,$(.FEATURES)))
undefine ConfirmUndefined
endif
endif
$(foreach var,$(SCRATCH_VARS),$(eval $(var):=))
# Variables to exclude from the save/restore/clear cycle
#
# These are internal to the operation of this file or otherwise special and
# must be excluded or things will get very confused.
NO_SAVE := _EXPORT _OVERRIDE SaveVariableIfSet RestoreVariable CheckSavedExportedVariable ProcessSavedVariable ClearUnsavedVariable SaveState RestoreState IncludeModule .%
# Initially just exclude scratch and internal variables from exports
# There are lots of others that should probably be added to this list...
# NOTE: Wrap names in quotes so we don't find match on partial words
RESERVED_VARS := $(SCRATCH_VARS) $(NO_SAVE)
RESERVED_VARS := $(RESERVED_VARS:%="%")
# Save a given variable
# NOTE: We must use a nested define to handle variables containing newlines
define SaveVariable
define _SAVE_$(1)
$(value $(1))
endef
endef
# Save a variable's value if it has been set in a file only
#
# NOTE: Here and elsewhere inside defines we cannot use standard conditionals
# as they behave in very obscure and seemingly buggy ways when used
# within templates.
define SaveVariableIfSet
$$(if $$(subst override,,$$(subst file,,$$(origin $(1)))),,$$(eval $$(call SaveVariable,$(1))))
endef
# Restore the value of a saved variable
define RestoreVariable
define $(1)
$(value _SAVE_$(1))
endef
endef
# Check a saved and exported variable has been appended to only
# TODO: Support variables declared as redefinable (exported and not append-only)
define CheckSavedExportedVariable
$$(if $$(findstring ^$$(value _SAVE_$(1)),^$$(value $(1))),,$$(error Saved and exported value $(1) is append-only))
endef
# Saved variables that were not exported are restored to their previous value
# Saved variables that were exported should have been appended to only
# Saved variables that were overriden can have any value
define ProcessSavedVariable
$$(if $$(findstring "$(1)",$$(_EXPORT)),$$(eval $$(call CheckSavedExportedVariable,$(1))),\
$$(if $$(findstring "$(1)",$$(_OVERRIDE)),,$$(eval $$(call RestoreVariable,$(1)))))
undefine _SAVE_$(1)
endef
# Clear the given variable if it has been set in the file and hasn't had a
# value saved or been exported/overriden
define ClearUnsavedVariable
$$(if $$(strip $$(subst override,,$$(subst file,,$$(origin $(1))))),,\
$$(if $$(strip $$(findstring "$(1)",$$(_EXPORT) $$(_OVERRIDE)) $$(subst undefined,,$$(origin _SAVE_$(1)))),,$$(eval undefine $(1))))
endef
# Save value of all other defined variables, excluding a few special ones
define SaveState
$$(foreach var,$$(filter-out $(NO_SAVE),$$(.VARIABLES)),$$(eval $$(call SaveVariableIfSet,$$(var))))
endef
# Check a variable is not on the reserved list
define CheckExport
$(if $(strip $(findstring "$(2)",$(RESERVED_VARS))),$$(error Module $(1) attempted to export/override reserved variable: $(2)),)
endef
# Restore to saved state, excluding any exported variables
define RestoreState
# Wrap each exported/overriden variable name in quotes
# NOTE: Need to take care to correctly handle (i.e. not reference) undefined EXPORT/OVERRIDE variables
_EXPORT := $$(and $$(filter-out undefined,$$(origin EXPORT)),$$(patsubst %,"%",$$(EXPORT)))
_OVERRIDE := $$(and $$(filter-out undefined,$$(origin OVERRIDE)),$$(patsubst %,"%",$$(OVERRIDE)))
# Check the module didn't export/override anything it shouldn't
# NOTE: Use quoted version defined above so as to correctly handle undefined EXPORT/OVERRIDE variables
$$(foreach var,$$(_EXPORT) $$(_OVERRIDE),$$(eval $$(call CheckExport,$(1),$$(patsubst "%",%,$$(var)))))
# Clear any variables that have not had values saved and were not exported
$$(foreach var,$$(filter-out _SAVE_% $(NO_SAVE),$$(.VARIABLES)),$$(eval $$(call ClearUnsavedVariable,$$(var))))
# Restore or check saved variables
$$(foreach var,$$(filter _SAVE_%,$$(.VARIABLES)),$$(eval $$(call ProcessSavedVariable,$$(patsubst _SAVE_%,%,$$(var)))))
# Manually clear/reset internal export/override list
undefine _EXPORT
undefine _OVERRIDE
endef
# A basic version that clears scratch variables and does an include.
#
# It will be used if make doesn't support undefine (i.e. version < 3.82).
define IncludeModule
$$(eval include $(1))
endef
ifdef .FEATURES
ifeq (undefine,$(filter undefine,$(.FEATURES)))
define IncludeModule
$$(eval $$(SaveState))
$$(eval include $(1))
$$(eval $$(call RestoreState,$(1)))
endef
endif
endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment