Add make1 -> env - make -R, whose -p output is easier to read.
[mgear/mgear.git] / src / mage.mk
CommitLineData
ac64c802
MM
1# Mage Build Tool by Matt McCutchen
2# http://www.kepreon.com/~matt/mage/
00804b7c 3
ac64c802 4# Remember the original default goal so we can restore it at the end of mage.mk.
ff59f3cf
MM
5mg-orig-default-goal:=$(.DEFAULT_GOAL)
6
7# We use second-expansion heavily to dynamically compute prerequisites when they
8# are needed. Second-expansion lets us follow make's implicit rule search
9# instead of trying to anticipate which prerequisites it will need in advance.
10.SECONDEXPANSION:
11
ac64c802
MM
12
13# TEXT UTILITIES
ff59f3cf
MM
14empty:=
15bs:=\$(empty)
16pct:=%
00804b7c
MM
17define nl
18
19
20endef
21# Shell-quote: a'b => 'a'\''b'
ff59f3cf 22sq='$(subst ','\'',$1)'
00804b7c
MM
23# Make-quote: a$\nb => a$$$(nl)b
24# This is enough to assign the value, *but not to use it as an argument!*
ff59f3cf 25mqas=$(subst $(nl),$$(nl),$(subst $$,$$$$,$1))
00804b7c
MM
26# Return nonempty if the strings are equal, empty otherwise.
27# If the strings have only nice characters, you can do $(filter x$1,x$2).
ff59f3cf 28streq=$(findstring x$1,$(findstring x$2,x$1))
00804b7c
MM
29
30# $(call fmt-make-assignment,foo,bar)
31# Output a make assignment that stores bar in variable foo.
32# The result is like `foo:=bar' but handles leading spaces, $, and
33# newlines appearing in bar safely.
ff59f3cf 34fmt-make-assignment=$1:=$$(empty)$(call mqas,$2)
00804b7c 35
ac64c802 36
8d481cf5
MM
37# TARGET OBFUSCATION
38
39# Mage uses two kinds of "obfuscated targets" (those that are not the simple
40# names of real files):
41# - An always-exists target like /.//. is used as a prerequisite of an implicit
42# rule. Since it exists, make doesn't second-expand its own prerequisites
43# until it is actually run. This way, a target's prerequisites can depend on
44# the results of previous command scripts.
45# - An alternative-name target like /.//./proc/self/cwd/bar is used to check the
46# mtime of a file without actually building it or introducing a circular
47# dependency.
48
49# $(newoid) allocates and returns a new obfuscation ID (oid). Example:
50# x:=$(newoid)
51# You can then refer to target $x or $x$(aname)bar (for any file bar).
52# Prerequisites and commands come from $($x@opr) and $($x@ocmd).
53# Target-specific variables $(oid) and $(otgt) (if alternate-name) are
54# available.
55
56opfx:=/./
57# TODO If the system doesn't support /proc/self/cwd, use something else.
58aname:=/proc/self/cwd/
59nextoid:=/./.
60newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.))))
61
ff59f3cf 62# Target-specific variables for obfuscated targets.
8d481cf5
MM
63$(opfx)%: oid=$(word 1,$(subst $(aname), ,$@))
64$(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@))
ff59f3cf
MM
65
66# High-priority implicit rule for obfuscated targets. Works for both always-
67# exists and alternate-name targets.
8d481cf5
MM
68$(opfx)%: $$($$(oid)@opr)
69 $($(oid)@ocmd)
00804b7c 70
ff59f3cf
MM
71# Make won't use the same implicit rule more than once on its stack, so provide
72# a second copy of the rule to allow two obfuscated targets on the stack.
73# Needed for $(mg-genfile-oid)$(aname)bar.g <- $(mg-scout-oid)$(aname)bar .
74# The prerequisites must look different before second-expansion so the second
75# rule isn't discarded as a duplicate.
76$(opfx)%: $$(empty) $$($$(oid)@opr)
77 $($(oid)@ocmd)
78
ac64c802
MM
79# MAIN BUILD LOGIC
80
ff59f3cf
MM
81# DESIGN:
82#
83# - To each generated file bar corresponds a "genfile" bar.g that contains some
84# information about how bar was generated, including the command (for rebuild on
85# command change) and the warnings (for replay).
86#
87# - bar.g's mtime is the last time Mage verified that bar was up to date. bar
88# needs to be regenerated iff a prerequisite is newer than *bar.g* (not bar).
89# If a prerequisite changes but the command gives the same contents for bar,
90# bar.g is touched but bar itself is not touched because files that depend on it
91# need not be regenerated.
92#
93# - If bar is newer than bar.g, the user has overridden it, and we should leave
94# the override in place but warn the user about it.
95#
96# - The user's Makefile defines Mage rules by calling mg-define-rule. Each Mage
97# rule becomes one underlying make rule with the same target and a little extra
98# magic. This is important so that all implicit rule competition takes place at
99# the same target. Additionally, the prerequisites are passed to an obfuscated
100# target for bar.g to see if any are newer than bar.g. This is done by the
101# single obfuscated implicit rule, keeping the number of implicit rules low.
102# Then the command script for bar checks for overrides, command change,
103# prerequisite change, etc. and acts accordingly.
104#
105# Target metadata variables for bar: (* means stored in bar.g)
cf5fd926
MM
106#
107# bar@cmd:=cat foo >bar.tmp
108# Generation command as given to the shell.*
ff59f3cf 109#
cf5fd926 110# bar@warnings:=$(empty)yikes!
15cf220e 111# Data that the command printed to stdout or stderr, presumably warnings.*
ff59f3cf 112#
cf5fd926
MM
113# bar@deps:=included@x oops@
114# If dependency-logging, list of filename@revision used. Revision is x for
115# exists and empty for doesn't exist. Later perhaps x will be the mtime.*
ff59f3cf 116#
cf5fd926
MM
117# bar@gloaded:=1
118# Set if Mage has loaded bar.g and hasn't changed it since then.
ff59f3cf
MM
119#
120# bar@gdeps:=foo
121# Static dependencies of bar, for checking by bar.g.
122#
123# bar@gq:=foo $(mg-scout-oid)$(aname)bar
124# $? from the rule for bar.g; used by the rule for bar.
125#
15cf220e
MM
126# bar@checked:=1
127# Set when Mage determines that a file is up to date or depends on it being
128# so determined. Used to decide which prerequisite to check next for a
129# dependency-logging command.
130#
131# bar@dlc-ran:=1
132# Indicates that Mage is in the middle of building bar using a dependency-
133# logging command. Means that bar.g.tmp, not bar.g, is the most current
134# genfile.
cf5fd926 135#
ff59f3cf
MM
136## If the rule for bar is overridden, we clear the information from bar.g so
137## that it is as if bar.g didn't exist. -- Not currently needed
138#
139# Some make features with which Mage's compatibility has not been investigated:
140# - Command-line options (especially --dry-run, --question, --touch,
141# --always-make, --keep-going, --jobs, --assume-old, and --assume-new)
142# - Static pattern rules
143# - Vpath
ac64c802
MM
144
145# $(call gload,foo.o)
00804b7c 146# Make sure foo.o's genfile, if any, has been loaded.
ac64c802
MM
147define gload
148$(if $($1@gloaded),,$(eval
149$1@cmd:=
150$1@warnings:=
151$1@deps:=
00804b7c 152-include $1.g
ac64c802 153$1@gloaded:=1
00804b7c
MM
154))
155endef
156
ff59f3cf
MM
157# bar.g: scout bar and its dependencies and store $?. When implicit rules
158# compete for bar, we depend on the rule make uses being the last one it
159# second-expands so that $(bar@gdeps) is still correct.
160mg-genfile-oid:=$(newoid)
161mg-scout-oid:=$(newoid)
162$(mg-genfile-oid)@opr=$($(otgt:.g=)@gdeps) $(if $(wildcard $(otgt:.g=)),$(mg-scout-oid)$(aname)$(otgt:.g=),)
163$(mg-genfile-oid)@ocmd=$(eval $(otgt:.g=)@gq:=$?)
164
165# Mage-ized automatic variables.
166# $@: needs no translation
167# NOTE: $(mg@) is *eventual* target. Commands must write to temp file, $t .
168# $%: haven't thought about it much, but probably needs no translation
169mg< = $(firstword $(mg^))
170mg? = $(filter-out $(opfx)%,$($@@gq))
171mg^ = $(filter-out MG-% $(opfx)%,$^)
172mg+ = $(filter-out MG-% $(opfx)%,$+)
173# $|: needs no translation
174# $*: needs no translation
175
176# $(call mg-translate-cmd,cat $$< >$$t)
177# Replaces references to automatic variables with references to their Mage-ized
178# counterparts. There might be false matches, e.g., $$@ => $$(mg@) ;
179# to prevent that, write $$$(empty)@ instead. (c.f. autoconf empty quadrigraph)
00804b7c 180define mg-translate-cmd
ff59f3cf 181$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
00804b7c
MM
182endef
183
ff59f3cf
MM
184# $(call prereq-predict,target,prerequisites)
185# Expands to code that does the following at second-expansion time:
186# 1. Computes the actual prerequisites from the given prerequisite patterns by
187# translating % to $* and prepending a directory if appropriate.
188# 2. Saves both existing ($+) and newly computed prerequisites to $(bar@gdeps)
189# where the rule for bar.g can get them.
190# Factor out set-gdeps because make gets confused if the macro for the
191# prerequisites has a colon. Make replaces the first % with the stem *before
192# expansion*, so use $(pct) to protect some %s.
193set-gdeps=$(eval $@@gdeps:=$1)
194prereq-predict=$$$$(call set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2))))
195
196# Used to make the rule for bar always run so we can act based on $(bar@gdeps)
197# and check for command change.
ac64c802 198MG-FORCE:
ce8fee85 199.PHONY: MG-FORCE
ff59f3cf
MM
200
201# $(call mg-define-rule,target,prerequisites,cmd)
202# Defines a rule. cmd is expanded again when it is run, at which time
203# Mage-ized automatic variables are available.
ac64c802 204define mg-define-rule
00804b7c
MM
205$(eval
206
ff59f3cf
MM
207# Store the new command.
208$1: cmd=$(call mg-translate-cmd,$3)
209# Provide variable for the temporary file. FIX
210$1: t=$$@.tmp
00804b7c 211
ff59f3cf
MM
212# Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new
213# prerequisites. The command script always runs. Depend on bar.g to get
214# $(bar@gq).
215$1: $(call prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g
216 $$(mg-rule-cmd)
00804b7c
MM
217)
218endef
219
ff59f3cf
MM
220# TODO Provide a way to define static pattern rules.
221
ac64c802 222# Procedure to generate bar. Remember, $@ is bar.g.
00804b7c 223# If an override:
ac64c802
MM
224# 1. Complain. (Should we stop "`bar' is up to date" using @:; ?)
225# 2. Clear out the warnings so we don't replay them.
226# Otherwise, check prereqs, command, and nonexistence to decide whether bar
227# needs to be regenerated. If so:
228# 1. Set a flag so Mage knows to reread the genfile when make runs target
229# "bar".
230# 2. Echo the command being run.
231# On error, skip to 8:
232# 3. Open the new genfile bar.g.tmp for writing.
233# 4. Store the command in bar.g.tmp.
234# 5. Run the command to bar.tmp, storing warnings in bar.g.tmp.
235# 6. If bar.tmp differs from bar:
236# a. Clear and touch bar.g (so bar doesn't become an override if we're
237# killed between steps 6b and 7).
238# b. Move in the new bar.
239# 7. Now that bar is known to be up to date, move in the new bar.g.
240# 8. Defensively remove both temporary files and exit with the exit code
241# from before the removal. (trap EXIT)
ff59f3cf
MM
242# If not:
243# 1. If there were warnings, replay them. (HMMM To stderr?)
244# HMMM .tmp in displayed command looks ugly
245define mg-rule-cmd
15cf220e 246 $(foreach x,$@ $(mg+),$(eval $x@checked:=1))
ff59f3cf
MM
247 $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\
248 $(info Mage: warning: Manually created/modified file at $@ overrides rule.)\
249 ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,x)$(mg-check-cmd),\
250 $(eval $@@gloaded:=)$(info $(cmd))\
251 @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\
252 exec 3>$@.g.tmp && $(mg-assign-cmd) >&3 &&\
253 set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
254 $(mg-maybe-move-target) && mv -f $@.g.tmp $@.g\
255 ,$(if $($@@warnings),\
256 $(info $($@@cmd) # Mage warning replay$(nl)$($@@warnings))\
257 )))
cf5fd926
MM
258endef
259
ff59f3cf
MM
260# If the command changed, we must regenerate.
261mg-check-cmd=$(if $(call streq,$(cmd),$($@@cmd)),,x)
cf5fd926 262
ff59f3cf
MM
263# Pieces of mg-generate that I factored out to make mg-generate more readable.
264mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$(cmd)))
265mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $@.tmp ] || { echo 'Mage: error: Command for $@ succeeded without creating it!'; false; }; }; }
266mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/'
267mg-maybe-move-target={ cmp -s $@ $@.tmp || echo >$@.g && mv -f $@.tmp $@; }
268
269# Just add additional prerequisites. This cannot add prerequisite patterns to
270# an implicit rule, but it can add specific prerequisites to an individual use
271# of an implicit rule. Currently, Mage picks up the target's prerequisites from
272# make, so this just attaches the given prerequisites to the target, but the
273# implementation might change in the future.
274define mg-define-prereq
275$(eval $1: $2)
cf5fd926
MM
276endef
277
ac64c802
MM
278# END
279
00804b7c 280# We don't want anything we defined to become the default goal.
ac64c802 281.DEFAULT_GOAL := $(mg-orig-default-goal)