X-Git-Url: https://mattmccutchen.net/mgear/mgear.git/blobdiff_plain/1a4a165bb388a4560c01f23a36f99cc63163e58b..21a503712996ac153f7e7b79596d8f3a372c1a97:/src/mage.mk diff --git a/src/mage.mk b/src/mage.mk deleted file mode 100644 index 658474d..0000000 --- a/src/mage.mk +++ /dev/null @@ -1,281 +0,0 @@ -# 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) - -# 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: - - -# TEXT UTILITIES -empty:= -bs:=\$(empty) -pct:=% -define nl - - -endef -# Shell-quote: a'b => 'a'\''b' -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)) -# 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)) - -# $(call fmt-make-assignment,foo,bar) -# Output a make assignment that stores bar in variable foo. -# The result is like `foo:=bar' but handles leading spaces, $, and -# newlines appearing in bar safely. -fmt-make-assignment=$1:=$$(empty)$(call mqas,$2) - - -# 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:.=/.)))) - -# Target-specific variables for obfuscated targets. -$(opfx)%: oid=$(word 1,$(subst $(aname), ,$@)) -$(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@)) - -# High-priority implicit rule for obfuscated targets. Works for both always- -# exists and alternate-name targets. -$(opfx)%: $$($$(oid)@opr) - $($(oid)@ocmd) - -# Make won't use the same implicit rule more than once on its stack, so provide -# a second copy of the rule to allow two obfuscated targets on the stack. -# Needed for $(mg-genfile-oid)$(aname)bar.g <- $(mg-scout-oid)$(aname)bar . -# The prerequisites must look different before second-expansion so the second -# rule isn't discarded as a duplicate. -$(opfx)%: $$(empty) $$($$(oid)@opr) - $($(oid)@ocmd) - -# MAIN BUILD LOGIC - -# DESIGN: -# -# - To each generated file bar corresponds a "genfile" bar.g that contains some -# information about how bar was generated, including the command (for rebuild on -# command change) and the warnings (for replay). -# -# - bar.g's mtime is the last time Mage verified that bar was up to date. bar -# needs to be regenerated iff a prerequisite is newer than *bar.g* (not bar). -# If a prerequisite changes but the command gives the same contents for bar, -# bar.g is touched but bar itself is not touched because files that depend on it -# need not be regenerated. -# -# - If bar is newer than bar.g, the user has overridden it, and we should leave -# the override in place but warn the user about it. -# -# - The user's Makefile defines Mage rules by calling mg-define-rule. Each Mage -# rule becomes one underlying make rule with the same target and a little extra -# magic. This is important so that all implicit rule competition takes place at -# the same target. Additionally, the prerequisites are passed to an obfuscated -# target for bar.g to see if any are newer than bar.g. This is done by the -# single obfuscated implicit rule, keeping the number of implicit rules low. -# Then the command script for bar checks for overrides, command change, -# prerequisite change, etc. and acts accordingly. -# -# Target metadata variables for bar: (* means stored in bar.g) -# -# bar@cmd:=cat foo >bar.tmp -# Generation command as given to the shell.* -# -# bar@warnings:=$(empty)yikes! -# Data that the command printed to stdout or stderr, presumably warnings.* -# -# 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@gdeps:=foo -# Static dependencies of bar, for checking by bar.g. -# -# bar@gq:=foo $(mg-scout-oid)$(aname)bar -# $? from the rule for bar.g; used by the rule for bar. -# -# bar@checked:=1 -# Set when Mage determines that a file is up to date or depends on it being -# so determined. Used to decide which prerequisite to check next for a -# dependency-logging command. -# -# bar@dlc-ran:=1 -# Indicates that Mage is in the middle of building bar using a dependency- -# logging command. Means that bar.g.tmp, not bar.g, is the most current -# genfile. -# -## 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. -- Not currently needed -# -# Some make features with which Mage's compatibility has not been investigated: -# - Command-line options (especially --dry-run, --question, --touch, -# --always-make, --keep-going, --jobs, --assume-old, and --assume-new) -# - Static pattern rules -# - Vpath - -# $(call gload,foo.o) -# Make sure foo.o's genfile, if any, has been loaded. -define gload -$(if $($1@gloaded),,$(eval -$1@cmd:= -$1@warnings:= -$1@deps:= --include $1.g -$1@gloaded:=1 -)) -endef - -# bar.g: scout bar and its dependencies and store $?. When implicit rules -# compete for bar, we depend on the rule make uses being the last one it -# second-expands so that $(bar@gdeps) is still correct. -mg-genfile-oid:=$(newoid) -mg-scout-oid:=$(newoid) -$(mg-genfile-oid)@opr=$($(otgt:.g=)@gdeps) $(if $(wildcard $(otgt:.g=)),$(mg-scout-oid)$(aname)$(otgt:.g=),) -$(mg-genfile-oid)@ocmd=$(eval $(otgt:.g=)@gq:=$?) - -# Mage-ized automatic variables. -# $@: needs no translation -# NOTE: $(mg@) is *eventual* target. Commands must write to temp file, $t . -# $%: haven't thought about it much, but probably needs no translation -mg< = $(firstword $(mg^)) -mg? = $(filter-out $(opfx)%,$($@@gq)) -mg^ = $(filter-out MG-% $(opfx)%,$^) -mg+ = $(filter-out MG-% $(opfx)%,$+) -# $|: needs no translation -# $*: needs no translation - -# $(call mg-translate-cmd,cat $$< >$$t) -# 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 - -# $(call 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. -# 2. Saves both existing ($+) and newly computed prerequisites to $(bar@gdeps) -# where the rule for bar.g can get them. -# 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)))) - -# Used to make the rule for bar always run so we can act based on $(bar@gdeps) -# and check for command change. -MG-FORCE: -.PHONY: 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 - -# 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) -) -endef - -# TODO Provide a way to define static pattern rules. - -# Procedure to generate bar. Remember, $@ is 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. -# 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) -# If not: -# 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))\ - @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\ - exec 3>$@.g.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 $@.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) - -# 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-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/' -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) -endef - -# END - -# We don't want anything we defined to become the default goal. -.DEFAULT_GOAL := $(mg-orig-default-goal)