#*********************************************************************** # # Build system module framework # # Copyright (c) 2014-2016 Process Systems Enterprise Ltd # ALL RIGHTS RESERVED # # 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))) # # This framework can introduce significant overhead. It can also complicate # matters when trying to debug makefiles themselves by dumping variable and # rule definitions (e.g. using -p). To avoid these issues it may be disabled # (falling back to the limited functionality as discussed above) by setting # NO_MODULES=1. # # make NO_MODULES=1 # #*********************************************************************** # 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 # 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)) $(foreach var,$(SCRATCH_VARS),$(eval $(var):=)) endef ifdef .FEATURES ifeq (undefine,$(filter undefine,$(.FEATURES))) ifeq (,$(strip $(value NO_MODULES))) define IncludeModule $$(eval $$(SaveState)) $$(eval include $(1)) $$(eval $$(call RestoreState,$(1))) endef endif endif endif