X-Git-Url: https://mattmccutchen.net/mgear/mgear.git/blobdiff_plain/ac64c802d8c1d6bfe58ffd2584e3b3ef37aa4d89..cf5fd926c0fbe03d7991a0342c6a99605c60692e:/src/mage.mk diff --git a/src/mage.mk b/src/mage.mk index 47650b3..3a5181a 100644 --- a/src/mage.mk +++ b/src/mage.mk @@ -26,25 +26,64 @@ streq = $(findstring x$1,$(findstring x$2,x$1)) # newlines appearing in bar safely. fmt-make-assignment = $1:=$$(empty)$(call mqas,$2) -# OBFUSCATION - -# Coming soon. +# We use second-expansion heavily to dynamically compute prerequisites when they +# are needed. Second-expansion lets us follow make's implicit rule search +# instead of trying to anticipate which prerequisites it will need in advance. +.SECONDEXPANSION: -# High-priority implicit rule to ensure that we don't try to actually update -# obfuscated targets. TEMP -/./proc/self/cwd/%: - +# TARGET OBFUSCATION + +# Mage uses two kinds of "obfuscated targets" (those that are not the simple +# names of real files): +# - An always-exists target like /.//. is used as a prerequisite of an implicit +# rule. Since it exists, make doesn't second-expand its own prerequisites +# until it is actually run. This way, a target's prerequisites can depend on +# the results of previous command scripts. +# - An alternative-name target like /.//./proc/self/cwd/bar is used to check the +# mtime of a file without actually building it or introducing a circular +# dependency. + +# $(newoid) allocates and returns a new obfuscation ID (oid). Example: +# x:=$(newoid) +# You can then refer to target $x or $x$(aname)bar (for any file bar). +# Prerequisites and commands come from $($x@opr) and $($x@ocmd). +# Target-specific variables $(oid) and $(otgt) (if alternate-name) are +# available. + +opfx:=/./ +# TODO If the system doesn't support /proc/self/cwd, use something else. +aname:=/proc/self/cwd/ +nextoid:=/./. +newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.)))) + +# High-priority implicit rule for obfuscated targets. Works for both always- +# exists and alternate-name targets. +$(opfx)%: oid=$(word 1,$(subst $(aname), ,$@)) +$(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@)) +$(opfx)%: $$($$(oid)@opr) + $($(oid)@ocmd) # MAIN BUILD LOGIC -# bar.g file format: -# bar@cmd:=cat foo >bar.tmp -# bar@warnings:=$(empty)yikes! -# And if dependency-logging: -# List of filename@revision, revision is x for exists and empty for doesn't -# exist. Later perhaps x will be the mtime. -# bar@deps:=included@x oops@ -# If bar is overridden, we clear all three variables. +# Target metadata variables for bar: (*: stored in bar.g) +# +# bar@cmd:=cat foo >bar.tmp +# Generation command as given to the shell.* +# bar@warnings:=$(empty)yikes! +# Stuff the command printed to stdout or stderr.* +# bar@deps:=included@x oops@ +# If dependency-logging, list of filename@revision used. Revision is x for +# exists and empty for doesn't exist. Later perhaps x will be the mtime.* +# bar@gloaded:=1 +# Set if Mage has loaded bar.g and hasn't changed it since then. +# bar@uptodate:=1 +# Set when Mage makes a file or depends on it being made. Used to determine +# the next prerequisite to check for a dependency-logging command. +# bar@generated:=1 +# Set when Mage generates a file; suppresses warning replay. +# +# If the rule for bar is overridden, we clear the information from bar.g so that +# it is as if bar.g didn't exist. # $(call gload,foo.o) # Make sure foo.o's genfile, if any, has been loaded. @@ -67,11 +106,7 @@ define mg-translate-cmd $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) endef -# OOOH!!! .SECONDEXPANSION does let us watch the implicit rule search as it -# happens. -.SECONDEXPANSION: - -# $(call mg-rule,target,prerequisite,cmd) +# $(call mg-define-rule,target,prerequisite,cmd) # Defines a rule. # If cmd uses $@, quote if necessary so this function sees $@, etc. # @@ -85,8 +120,8 @@ endef # - bar doesn't exist or is managed and out of date => generate # - bar is unmanaged => warn about override MG-FORCE: -MG-FORCE-TARGET-DNE: -.PHONY: MG-FORCE MG-FORCE-TARGET-DNE +.PHONY: MG-FORCE +mg-scout-oid:=$(newoid) define mg-define-rule $(eval @@ -95,8 +130,8 @@ $(eval $1.g: target = $$(@:.g=) $1.g: cmd = $(call mg-translate-cmd,$3) $1.g: mg@ = $$(target).tmp -$1.g: mg^ = $$(filter-out MG-% /./%,$$^) -$1.g: mg+ = $$(filter-out MG-% /./%,$$+) +$1.g: mg^ = $$(filter-out MG-% $$(opfx)%,$$^) +$1.g: mg+ = $$(filter-out MG-% $$(opfx)%,$$+) $1.g: mg< = $$(firstword $$(mg^)) # Rule for the genfile. Evidently all the prerequisites we want second-expanded @@ -104,27 +139,31 @@ $1.g: mg< = $$(firstword $$(mg^)) $1.g: $2 $$$$(mg-scout-target) MG-FORCE $$(mg-generate) +$(mg-file-from-genfile) +) +endef + +define mg-file-from-genfile # If the file was regenerated, load the new genfile. # If not, replay any warnings. -## We don't have to worry about .DELETE_ON_ERROR deleting the target because -## it is only touched if we *successfully* regenerate it. -# - No longer applies because we don't remember errors. # HMMM Maybe errors should go to stderr??? $1: MG-FORCE | $1.g $$(call gload,$$@) - $$(if $$($$@@warnings),$$(if $$($$@@built),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),) -) + $$(eval $$@@uptodate:=1) + $$(if $$($$@@warnings),$$(if $$($$@@changed),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),) endef # If the target is unmanaged, we must run the rule; we'll see that the # obfuscated target is in $? and complain. -mg-scout-target=$(if $(wildcard $(target)),/./proc/self/cwd/$(target),) +mg-scout-target=$(if $(wildcard $(target)),$(mg-scout-oid)$(aname)$(target),) # If the command changed, we must regenerate. mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x) # Just add additional prerequisites. -# I don't think this works for implicit rules. +# I don't think this can add patterns to implicit rules, but it should be able +# to add specific prerequisites to uses of implicit rules. +# TODO Make this work for dependency-logging commands if desired. define mg-define-prereq $(eval $1.g: $2 @@ -153,12 +192,12 @@ endef # from before the removal. (trap EXIT) define mg-generate $(call gload,$(target))\ - $(if $(filter /./proc/self/cwd/$(target),$?),\ + $(if $(filter $(mg-scout-oid)$(aname)$(target),$?),\ $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\ $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\ ,\ $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\ - $(eval $(target)@gloaded:=)$(eval $(target)@built:=1)$(info $(cmd))\ + $(eval $(target)@gloaded:=)$(eval $(target)@changed:=1)$(info $(cmd))\ @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\ exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\ set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ @@ -172,6 +211,43 @@ mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Com mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/' mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); } +# $(call mg-rule,target,static prerequisites,cmd,[dep converter]) +# Defines a rule with a dependency-logging command. +# I haven't decided on the format for the dep converter yet. +dlc-static-run-oid:=$(newoid) +define mg-define-rule-dlc +$(eval + +# Copied stuff from mg-define-rule to modify as necessary + +## Define some target-specific variables. +## It might look like we could use $*, but $1 most likely isn't %. +#$1.g: target = $$(@:.g=) +#$1.g: cmd = $(call mg-translate-cmd,$3) +#$1.g: mg@ = $$(target).tmp +#$1.g: mg^ = $$(filter-out MG-% /./%,$$^) +#$1.g: mg+ = $$(filter-out MG-% /./%,$$+) +#$1.g: mg< = $$(firstword $$(mg^)) + +# +$1.g: MG-FORCE | $$$$(dlc-first-run-oid)$$$$(aname)$1.g $$$$(call dlc-setup-tgt,$(newoid)) + +## Rule for the genfile. Evidently all the prerequisites we want second-expanded +## have to go on the same rule. +#$1.g: $2 $$$$(mg-scout-target) MG-FORCE +# $$(mg-generate) + + +$(mg-file-from-genfile) +) +endef + +define dlc-setup-tgt +$(eval + +)$1 +endef + # END # We don't want anything we defined to become the default goal.