- Revise and comment target metadata variables.
[mgear/mgear.git] / src / mage.mk
index 47650b3..3a5181a 100644 (file)
@@ -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.