From: Matt McCutchen Date: Mon, 4 Jun 2007 05:42:45 +0000 (-0400) Subject: Made a bunch of cleanups/improvements, including: X-Git-Url: https://mattmccutchen.net/mgear/mgear.git/commitdiff_plain/ac64c802d8c1d6bfe58ffd2584e3b3ef37aa4d89 Made a bunch of cleanups/improvements, including: - Changed some variable/function names. mg-rule -> mg-define-rule. (Changed demo to match.) Changed Mage target variables: cmd-foo.o -> foo.o@cmd. - Took out exit code replay because it introduced lots of complexity for little benefit. - Changed scouted target from abspath to /./proc/self/cwd format. This fits with the planned target obfuscation system but isn't so portable; I'll reconsider later. - Massively refactored mg-commands -> mg-generate so that it's understandable. - FORCEd *.g rules and moved a bunch of processing from second-expansion to the command script. This way the processing only runs if make actually uses the rule (not just second-expanding anyway or implicit rule searching). - Detection of command change for implicit rules was broken because evidently only the prereqs given with the command take effect. I moved all the prereqs to the same line but then the new command was evaluated too early to see the automatic variables with the user-specified prereqs. Moving the command check into the command script fixed it. - Put more complete title and Web site at the top. I have been preparing to add the dependency-logging command support, which is going to be dauntingly complicated. I wanted to nail down the basics first. --- diff --git a/demo/Makefile b/demo/Makefile index 2e20b36..cfdfc32 100644 --- a/demo/Makefile +++ b/demo/Makefile @@ -7,7 +7,7 @@ include mage.mk foo.x.h: cmd-xgrep = grep 'X' '$<' >'$@' -$(call mg-rule,%.x,%,$(value cmd-xgrep)) +$(call mg-define-rule,%.x,%,$(value cmd-xgrep)) cmd-addheader = echo 'Header:' | cat - '$<' >'$@' -$(call mg-rule,%.h,%,$(value cmd-addheader)) +$(call mg-define-rule,%.h,%,$(value cmd-addheader)) diff --git a/src/mage.mk b/src/mage.mk index 465a05a..47650b3 100644 --- a/src/mage.mk +++ b/src/mage.mk @@ -1,6 +1,10 @@ -# Mage by Matt McCutchen +# Mage Build Tool by Matt McCutchen +# http://www.kepreon.com/~matt/mage/ -# Text utilities +# Remember the original default goal so we can restore it at the end of mage.mk. +mg-orig-default-goal := $(.DEFAULT_GOAL) + +# TEXT UTILITIES empty := bs := \$(empty) define nl @@ -22,29 +26,42 @@ streq = $(findstring x$1,$(findstring x$2,x$1)) # newlines appearing in bar safely. fmt-make-assignment = $1:=$$(empty)$(call mqas,$2) -mg-orig-default-goal := $(.DEFAULT_GOAL) +# OBFUSCATION -# bar.g file format: -# cmd-bar:=cat foo >bar.tmp -# errors-bar:=$(empty)yikes! -# exitcode-bar:=0 +# Coming soon. + +# High-priority implicit rule to ensure that we don't try to actually update +# obfuscated targets. TEMP +/./proc/self/cwd/%: + -# $(call mg-check-file,foo.o) +# 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. + +# $(call gload,foo.o) # Make sure foo.o's genfile, if any, has been loaded. -define mg-check-file -$(if $(mg-checked-$1),,$(eval -cmd-$1 := -errors-$1 := -exitcode-$1 := +define gload +$(if $($1@gloaded),,$(eval +$1@cmd:= +$1@warnings:= +$1@deps:= -include $1.g -mg-checked-$1 := done +$1@gloaded:=1 )) endef # $(call mg-translate-cmd,touch $$@) -# Currently translates $@, $<, $^, $+ to Mage-ized variables. -# $* needs no translation. We won't support $%, $?, $|. -# There might be false matches, e.g., $$@ => $$(out) ; +# Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary. +# We won't support $%, $?, $|. +# There might be false matches, e.g., $$@ => $$(mg@) ; # to prevent that, do $$$(empty)@ . define mg-translate-cmd $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) @@ -58,19 +75,19 @@ endef # Defines a rule. # If cmd uses $@, quote if necessary so this function sees $@, etc. # -# When we build bar from foo using a Mage rule: +# When we generate bar from foo using a Mage rule: # Division of work: -# - "bar.g: foo" does the computation. -# - "bar: bar.g" replays errors and exit code. +# - "bar.g: foo" generates bar if necessary. +# - "bar: bar.g" loads new genfile and replays warnings if bar.g was +# already up to date. # Cases: # - bar is managed and up to date => replay -# - bar doesn't exist or is managed and out of date => compute +# - bar doesn't exist or is managed and out of date => generate # - bar is unmanaged => warn about override +MG-FORCE: MG-FORCE-TARGET-DNE: -MG-FORCE-CMD-CHANGED: -MG-FORCE-REPLAY: -.PHONY: MG-FORCE-TARGET-DNE MG-FORCE-CMD-CHANGED MG-FORCE-REPLAY -define mg-rule +.PHONY: MG-FORCE MG-FORCE-TARGET-DNE +define mg-define-rule $(eval # Define some target-specific variables. @@ -78,68 +95,84 @@ $(eval $1.g: target = $$(@:.g=) $1.g: cmd = $(call mg-translate-cmd,$3) $1.g: mg@ = $$(target).tmp -$1.g: mg^ = $$(filter-out MG-% $$(abspath $$(target)),$$^) -$1.g: mg+ = $$(filter-out MG-% $$(abspath $$(target)),$$+) +$1.g: mg^ = $$(filter-out MG-% /./%,$$^) +$1.g: mg+ = $$(filter-out MG-% /./%,$$+) $1.g: mg< = $$(firstword $$(mg^)) -# Load the target's genfile if necessary. -$1.g: $$$$(call mg-check-file,$$$$(target)) +# 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) + +# 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))),) +) +endef -# If the target doesn't exist, we must run the rule; we'll generate it. # If the target is unmanaged, we must run the rule; we'll see that the # obfuscated target is in $? and complain. -$1.g: $$$$(if $$$$(wildcard $$$$(target)),$$$$(abspath $$$$(target)),$$$$(if $$(exitcode-$$$$(target)),,MG-FORCE-TARGET-DNE)) +mg-scout-target=$(if $(wildcard $(target)),/./proc/self/cwd/$(target),) # If the command changed, we must regenerate. -$1.g: $$$$(if $$$$(call streq,$$$$(cmd),$$$$(cmd-$$$$(target))),,MG-FORCE-CMD-CHANGED) - -$1.g: $2 - $(value mg-commands) - -# Replay the errors and the exit code (if any of either). -# We don't have to worry about .DELETE_ON_ERROR deleting the target because -# it is only touched if we *successfully* regenerate it. -# Recheck the file in case it was regenerated. -# HMMM Maybe errors should go to stderr??? -$1: $1.g MG-FORCE-REPLAY - $$(call mg-check-file,$$@) - $$(if $$(errors-$$@)$$(exitcode-$$@)$$(built-$$@),$$(info $$(cmd-$$@)$$(if $$(built-$$@),, [replay]))$$(if $$(errors-$$@),$$(info $$(errors-$$@)),),) - $$(if $$(exitcode-$$@),@exit $$(exitcode-$$@),) -) -endef +mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x) # Just add additional prerequisites. -define mg-prereq +# I don't think this works for implicit rules. +define mg-define-prereq $(eval $1.g: $2 ) endef +# Procedure to generate bar. Remember, $@ is bar.g. # If an override: -# 1. Complain. -# 2. Clear out the errors and exit code so we don't replay them. -# Otherwise: -# 1. Store the command. -# 2. Run the command, storing errors. -# 3. Store exit code. -# 4. Update the real files as applicable. -# 5. Read the new genfile. -define mg-commands - $(if $(filter $(abspath $(target)),$?),\ +# 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) +# 2. Clear out the warnings so we don't replay them. +# Otherwise, check prereqs, command, and nonexistence to decide whether bar +# needs to be regenerated. If so: +# 1. Set a flag so Mage knows to reread the genfile when make runs target +# "bar". +# 2. Echo the command being run. +# On error, skip to 8: +# 3. Open the new genfile bar.g.tmp for writing. +# 4. Store the command in bar.g.tmp. +# 5. Run the command to bar.tmp, storing warnings in bar.g.tmp. +# 6. If bar.tmp differs from bar: +# a. Clear and touch bar.g (so bar doesn't become an override if we're +# killed between steps 6b and 7). +# b. Move in the new bar. +# 7. Now that bar is known to be up to date, move in the new bar.g. +# 8. Defensively remove both temporary files and exit with the exit code +# from before the removal. (trap EXIT) +define mg-generate + $(call gload,$(target))\ + $(if $(filter /./proc/self/cwd/$(target),$?),\ $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\ - $(eval cmd-$(target):=)$(eval errors-$(target):=)$(eval exitcode-$(target):=)\ + $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\ ,\ - @$(eval mg-checked-$(target) :=)$(eval built-$(target) := yes)\ - exec 3>$@.tmp &&\ - echo $(call sq,$(call fmt-make-assignment,cmd-$(target),$(cmd))) >&3 &&\ - set -o pipefail &&\ - { { ($(cmd)) && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } 2>&1\ - | sed -re '1s/^/errors-$(target):=$$(empty)/; 1!s/^/errors-$(target)+=$$(nl)/' >&3; xc=$$?; } &&\ - { [ $$xc == 0 ] || echo "exitcode-$(target):=$$xc" >&3; } &&\ - if [ $$xc != 0 ] || cmp -s $(target) $(target).tmp; then mv -f $@.tmp $@ && rm -f $(target).tmp;\ - else rm -f $(target) && mv -f $@.tmp $@ && mv -f $(target).tmp $(target); fi\ - ) + $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\ + $(eval $(target)@gloaded:=)$(eval $(target)@built:=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 &&\ + $(mg-maybe-move-target) && mv -f $@.tmp $@\ + )) endef +# Pieces of mg-generate that I factored out to make mg-generate more readable. +mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd))) +mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } +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); } + +# END + # We don't want anything we defined to become the default goal. -.DEFAULT_GOAL := $(mg-orig-default-goal) \ No newline at end of file +.DEFAULT_GOAL := $(mg-orig-default-goal)