You want complex GNU makefiles? Alrighty then... Please find attached a small piece of our build system. It emulates local variables, essentially by saving the values of all variables prior to a makefile being included, then restoring them and clearing any new ones after. I wrote it to help us find bugs in and make our large (~20k LoC) and very complex build system more robust.
As an aside -- if anyone has any ideas on how to do this better and/or faster (it is very slow, ~doubling my no-op build times) I'd be very happy to hear them. I've been meaning to publish the full build system framework itself as open-source for ages (and I have agreement from my senior management to do that), but that requires first separating it into generic and build-specific parts, and ensuring none of our company-specific or proprietary bits are left in the former. Since it is so large and complex this is not easy. This bit, however, is already generic and should be usable on its own. Cheers, Duane. On Sat, Mar 18, 2017 at 5:45 AM, Brian Cowan <cowan...@us.ibm.com> wrote: > I'm chasing down use cases for the $(call and $(eval functions to > effectively build the makefile from macros in the makefile (or included > files). I'm looking to stress-test the make tool I support, which supports > GNU make syntax, but I'm finding that elementary usecases don't always > suffice. So, if someone knows of a makefile-from-h**l they have fought > with, I'd love to hear about it... If its in an open-source project, that > would be even better... > > > ============================================================= > Brian Cowan > ClearCase/Licensing Software Advisory Team (SWAT) > HCL Technologies at IBM > IBM Cloud Support > 550 King St > Littleton MA 01460 > > Phone: +1 978 899 5436 > > > _______________________________________________ > Help-make mailing list > Help-make@gnu.org > https://lists.gnu.org/mailman/listinfo/help-make > -- "I never could learn to drink that blood and call it wine" - Bob Dylan
#*********************************************************************** # # 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
_______________________________________________ Help-make mailing list Help-make@gnu.org https://lists.gnu.org/mailman/listinfo/help-make