From 21a503712996ac153f7e7b79596d8f3a372c1a97 Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Tue, 12 Jun 2007 21:31:06 -0400 Subject: [PATCH] Add a testsuite (./testsuite) and dependency-logging command stuff that doesn't do anything yet, make a bunch of minor improvements, and move mage.mk to the top level of the Mage tree. --- demo/mage.mk | 2 +- src/mage.mk => mage.mk | 110 ++++++++++++------- testsuite | 238 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+), 37 deletions(-) rename src/mage.mk => mage.mk (76%) create mode 100755 testsuite diff --git a/demo/mage.mk b/demo/mage.mk index 73c6b44..d428552 120000 --- a/demo/mage.mk +++ b/demo/mage.mk @@ -1 +1 @@ -../src/mage.mk \ No newline at end of file +../mage.mk \ No newline at end of file diff --git a/src/mage.mk b/mage.mk similarity index 76% rename from src/mage.mk rename to mage.mk index 658474d..d9ad5d7 100644 --- a/src/mage.mk +++ b/mage.mk @@ -1,6 +1,7 @@ # Mage Build Tool by Matt McCutchen # http://www.kepreon.com/~matt/mage/ + # Remember the original default goal so we can restore it at the end of mage.mk. mg-orig-default-goal:=$(.DEFAULT_GOAL) @@ -13,6 +14,7 @@ mg-orig-default-goal:=$(.DEFAULT_GOAL) # TEXT UTILITIES empty:= bs:=\$(empty) +hash:=\# pct:=% define nl @@ -22,7 +24,7 @@ endef sq='$(subst ','\'',$1)' # Make-quote: a$\nb => a$$$(nl)b # This is enough to assign the value, *but not to use it as an argument!* -mqas=$(subst $(nl),$$(nl),$(subst $$,$$$$,$1)) +mqas=$(subst $(hash),$$(hash),$(subst $(nl),$$(nl),$(subst $$,$$$$,$1))) # Return nonempty if the strings are equal, empty otherwise. # If the strings have only nice characters, you can do $(filter x$1,x$2). streq=$(findstring x$1,$(findstring x$2,x$1)) @@ -177,11 +179,9 @@ mg+ = $(filter-out MG-% $(opfx)%,$+) # Replaces references to automatic variables with references to their Mage-ized # counterparts. There might be false matches, e.g., $$@ => $$(mg@) ; # to prevent that, write $$$(empty)@ instead. (c.f. autoconf empty quadrigraph) -define mg-translate-cmd -$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) -endef +mg-translate-cmd=$(subst $$t,$$@.tmp,$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))) -# $(call prereq-predict,target,prerequisites) +# $(call mg-prereq-predict,target,prerequisites) # Expands to code that does the following at second-expansion time: # 1. Computes the actual prerequisites from the given prerequisite patterns by # translating % to $* and prepending a directory if appropriate. @@ -190,8 +190,8 @@ endef # Factor out set-gdeps because make gets confused if the macro for the # prerequisites has a colon. Make replaces the first % with the stem *before # expansion*, so use $(pct) to protect some %s. -set-gdeps=$(eval $@@gdeps:=$1) -prereq-predict=$$$$(call set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2)))) +mg-set-gdeps=$(eval $@@gdeps:=$1) +mg-prereq-predict=$$$$(call mg-set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2)))) # Used to make the rule for bar always run so we can act based on $(bar@gdeps) # and check for command change. @@ -201,37 +201,33 @@ MG-FORCE: # $(call mg-define-rule,target,prerequisites,cmd) # Defines a rule. cmd is expanded again when it is run, at which time # Mage-ized automatic variables are available. -define mg-define-rule -$(eval - -# Store the new command. -$1: cmd=$(call mg-translate-cmd,$3) -# Provide variable for the temporary file. FIX -$1: t=$$@.tmp - +# +# I eradicated the target-specific variables because they fail when there are +# multiple implicit rules with the same target pattern. +# # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new # prerequisites. The command script always runs. Depend on bar.g to get # $(bar@gq). -$1: $(call prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g - $$(mg-rule-cmd) -) +define mg-define-rule +$(eval $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g + $$(call mg-rule-cmd,$(call mg-translate-cmd,$3))) endef # TODO Provide a way to define static pattern rules. -# Procedure to generate bar. Remember, $@ is bar.g. +# Procedure to generate bar. Now $@ is bar, so we say $@.g for bar.g. # If an override: # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) -# 2. Clear out the warnings so we don't replay them. +# 2. This would be the place to clear bar's metadata if we wanted to. # 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". +# 1. Note that the genfile is about to change. # 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. +# 4. Run the command to bar.tmp, storing warnings in bar.g.tmp. +# 5. Store the command in bar.g.tmp. Do it here to ensure that bar.g.tmp +# is at least as new as bar.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). @@ -243,39 +239,81 @@ endef # 1. If there were warnings, replay them. (HMMM To stderr?) # HMMM .tmp in displayed command looks ugly define mg-rule-cmd - $(foreach x,$@ $(mg+),$(eval $x@checked:=1)) $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\ $(info Mage: warning: Manually created/modified file at $@ overrides rule.)\ - ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,x)$(mg-check-cmd),\ - $(eval $@@gloaded:=)$(info $(cmd))\ + ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,TARGET-DNE)$(mg-check-cmd),\ + $(eval $@@gloaded:=)$(info $1)\ @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\ - exec 3>$@.g.tmp && $(mg-assign-cmd) >&3 &&\ + exec 3>$@.g.tmp &&\ set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ - $(mg-maybe-move-target) && mv -f $@.g.tmp $@.g\ + $(mg-assign-cmd) >&3 &&\ + $(mg-maybe-move-target) &&\ + mv -f $@.g.tmp $@.g\ ,$(if $($@@warnings),\ $(info $($@@cmd) # Mage warning replay$(nl)$($@@warnings))\ ))) endef # If the command changed, we must regenerate. -mg-check-cmd=$(if $(call streq,$(cmd),$($@@cmd)),,x) +mg-check-cmd=$(if $(call streq,$1,$($@@cmd)),,COMMAND-CHANGED) # Pieces of mg-generate that I factored out to make mg-generate more readable. -mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$(cmd))) -mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $@.tmp ] || { echo 'Mage: error: Command for $@ succeeded without creating it!'; false; }; }; } +mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$1)) +mg-run-cmd={ ($1) 2>&1 && { [ -r $@.tmp ] || { echo 'Mage: error: Command for $@ succeeded without creating it!'; false; }; }; } mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/' -mg-maybe-move-target={ cmp -s $@ $@.tmp || echo >$@.g && mv -f $@.tmp $@; } +# Drat bash's lack of precedence between || and &&. Extra braces necessary. +mg-maybe-move-target={ cmp -s $@ $@.tmp || { echo >$@.g && mv -f $@.tmp $@; }; } # Just add additional prerequisites. This cannot add prerequisite patterns to # an implicit rule, but it can add specific prerequisites to an individual use # of an implicit rule. Currently, Mage picks up the target's prerequisites from # make, so this just attaches the given prerequisites to the target, but the # implementation might change in the future. -define mg-define-prereq -$(eval $1: $2) +mg-define-prereq=$(eval $1: $2) + + +# DEPENDENCY-LOGGING COMMANDS + +mg-dlc-static-run-oid:=$(mg-genfile-oid) + +# $(call mg-define-rule-dlc,target,static-prerequisites,cmd,dep-converter) +# Analogue of mg-define-rule for a dependency-logging command. +# I haven't decided on the format for the dep-converter yet. +define mg-define-rule-dlc +$(eval + +# FINISH + +# Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new +# prerequisites. The command script always runs. Finally, do the static run +# and then the first dynamic check. +$1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-dlc-static-run-oid)$(aname)$$$$@.g $$$$(call dlc-next-dchk,$$$$(target)) +# TODO: Move the finished file into place or something +# $$(call mg-rule-cmd,$3) +) endef -# END +#mg-dlc-next-dchk=$(call mg-dlc-next-dchk-1,$1,$(newoid)) +#mg-dlc-next-dchk-1=$(eval $2@opr=$$(call mg-dlc-dchk-pr,$1,$2))$2 +# +## $(call mg-dlc-dchk-pr,target,oid) +#mg-dlc-dchk-pr=$(call mg-dlc-dchk-pr-1,$1,$2,$(call next-unchecked-prereq,$($1@deps))) +## $(call mg-next-unchecked-prereq,foo.c bar.h baz.h) +#mg-next-unchecked-prereq=$(firstword $(foreach p,$1,$(if $($p@checked),,$p))) +#define mg-dlc-dchk-pr-1 +#$(if $3,$(call mg-dlc-drun,$1,$2,$3) $(call mg-dlc-next-dchk,$1),) +#endef +# +## $(call mg-dlc-drun,target,oid,prereq). FINISH +#define mg-dlc-drun +#$(eval +#$2$(aname)$3@opr:=$3 +#$2$(aname)$3@ocmd:= +#)$2$(aname)$3 +#endef +# +# +## END # We don't want anything we defined to become the default goal. .DEFAULT_GOAL := $(mg-orig-default-goal) diff --git a/testsuite b/testsuite new file mode 100755 index 0000000..3329a66 --- /dev/null +++ b/testsuite @@ -0,0 +1,238 @@ +#!/bin/bash +# Test suite for Mage. + +echo "Test suite for Mage" + +cd "$(dirname "$0")" +Z=test-zone +rm -rf $Z +mkdir $Z +cd $Z + +#exec 3>&2 +#exec 1>test.log 2>&1 +exec &2' ERR +#set -x + +ln -s ../mage.mk mage.mk + +function fail { + false +} + +function start_section { + section="$1" + echo + echo "SECTION: $1" +} + +function do_mage { + echo "Running: make $*" + make "$@" 2>&1 | tee mage.log +} + +function assert_contents { + if diff -u - "$1"; then + echo "File '$1' looks good." + else + echo "File '$1' has the wrong contents!" + fail + fi +} + +# Options can be passed to grep: assert_saw -i override +function assert_saw { + if grep -q "$@" mage.log; then + echo "Good, saw '${@:$#}' in build log." + else + echo "Expected '${@:$#}' in build log but didn't see it! Log:" + cat mage.log + fail + fi +} +function assert_not_saw { + if ! grep -q "$@" mage.log; then + echo "Good, saw '${@:$#}' in build log." + else + echo "Did not expect '${@:$#}' in build log but saw it! Log:" + cat mage.log + fail + fi +} +function assert_uptodate { + assert_saw "make: \`$1' is up to date." +} +function assert_generated { + assert_saw "$1.tmp" +} + +function remember_mtime { + while [ $# != 0 ]; do + eval "orig_mtime_${1//[^A-Za-z]/}=$(stat --format=%Y $1)" + shift + done +} +function assert_touched { + mtvar=orig_mtime_${1//[^A-Za-z]/} + t1="${!mtvar}" + t2="$(stat --format=%Y $1)" + echo "Times for '$1': $t1, $t2" + if [ "$t1" != "$t2" ]; then + echo "Good, '$1' was touched." + else + echo "Expected '$1' to be touched but it wasn't!" + fail + fi +} +function assert_not_touched { + mtvar=orig_mtime_${1//[^A-Za-z]/} + t1="${!mtvar}" + t2="$(stat --format=%Y $1)" + echo "Times for '$1': $t1, $t2" + if [ "$t1" == "$t2" ]; then + echo "Good, '$1' was not touched." + else + echo "Expected '$1' to be not touched but it was!" + fail + fi +} + +# Simple makefile for stripping two kinds of comments. +# Tests an implicit rule and two competing explicit rules. +# Watch those dollar signs! +cat >Makefile <<'EOF' +include mage.mk +.SECONDARY: +include hc-rule.mk +$(call mg-define-rule,%,%.ssc,sleep 1 && grep 'warn' $$< && sed -e 's_//.*$$$$__' $$< >$$t) +$(call mg-define-rule,index,index.in,sort $$< >$$t) +EOF +cat >hc-rule.mk <<'EOF' +$(call mg-define-rule,%,%.hc,sed -e 's_#.*$$$$__' $$< >$$t) +EOF + +# Input files. +cat >foo.hc <<'EOF' +This is the foo file. +# You don't get to see this. +But you do get to see this! +// Needles: you can lean but you can't hide! +EOF +cat >bar.ssc <<'EOF' +the bar file has a different personality +// hey there +# I slip through +warn: tell me about it! +EOF +cat >index.in <<'EOF' +foo +bar +EOF + +# Run and make sure the files were compiled correctly. + +start_section "Initial full build" +do_mage foo bar index + +assert_contents foo <<'EOF' +This is the foo file. + +But you do get to see this! +// Needles: you can lean but you can't hide! +EOF +assert_contents bar <<'EOF' +the bar file has a different personality + +# I slip through +warn: tell me about it! +EOF +assert_contents index <<'EOF' +bar +foo +EOF +assert_generated foo +assert_generated bar +assert_generated index +assert_saw "sed -e 's_#.*\$__' foo.hc >foo.tmp" +assert_saw "sleep 1 && grep 'warn' bar.ssc && sed -e 's_//.*\$__' bar.ssc >bar.tmp" +assert_saw 'warn: tell me about it!' +assert_saw sort index.in >index.tmp + +# Run it again. Make sure the warning is replayed and bar isn't overridden due +# to bar.g accidentally being too old. + +start_section "Replay bar warning" +do_mage foo bar index + +assert_uptodate foo +assert_uptodate bar +assert_uptodate index +assert_saw -i '#.*warning.*replay' # Indication of warning replay +assert_saw 'warn: tell me about it!' # Actual warning +assert_not_saw -i overrid + +# Now override bar and make sure it stays that way and the warning isn't replayed. +start_section "Override bar" +sleep 1 # No racy cleanliness +echo NEWCONTENT >bar +do_mage bar + +assert_saw -i overrid +assert_contents bar <<>foo.hc +do_mage index foo + +assert_uptodate index +assert_generated foo +assert_contents foo <<'EOF' +This is the foo file. + +But you do get to see this! +// Needles: you can lean but you can't hide! +look: +EOF + +# Change the rule for # comments to strip spaces before a #. +# Make sure foo is updated properly. + +start_section "Command change for % <- %.hc" +cat >hc-rule.mk <<'EOF' +$(call mg-define-rule,%,%.hc,sed -e 's_ *#.*$$$$__' $$< >$$t) +EOF +do_mage foo + +assert_generated foo +assert_contents foo <<'EOF' +This is the foo file. + +But you do get to see this! +// Needles: you can lean but you can't hide! +look: +EOF + +# Remove a space before a # from foo.hc. This is an inconsequential change. +# Make sure that foo.g is touched but foo is not. + +start_section "Inconsequential change to foo.hc" +sleep 1 # No racy cleanliness +remember_mtime foo foo.g +sed -e '$s/look: /look:/' -i foo.hc +do_mage foo + +assert_generated foo +assert_touched foo.g +assert_not_touched foo + +cd .. +rm -rf test-zone +echo +echo "TEST SUITE SUCCEEDED" -- 2.34.1