- Revise and comment target metadata variables.
[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
MM
4# Remember the original default goal so we can restore it at the end of mage.mk.
5mg-orig-default-goal := $(.DEFAULT_GOAL)
6
7# TEXT UTILITIES
00804b7c
MM
8empty :=
9bs := \$(empty)
10define nl
11
12
13endef
14# Shell-quote: a'b => 'a'\''b'
15sq = '$(subst ','\'',$1)'
16# Make-quote: a$\nb => a$$$(nl)b
17# This is enough to assign the value, *but not to use it as an argument!*
18mqas = $(subst $(nl),$$(nl),$(subst $$,$$$$,$1))
19# Return nonempty if the strings are equal, empty otherwise.
20# If the strings have only nice characters, you can do $(filter x$1,x$2).
21streq = $(findstring x$1,$(findstring x$2,x$1))
22
23# $(call fmt-make-assignment,foo,bar)
24# Output a make assignment that stores bar in variable foo.
25# The result is like `foo:=bar' but handles leading spaces, $, and
26# newlines appearing in bar safely.
27fmt-make-assignment = $1:=$$(empty)$(call mqas,$2)
28
8d481cf5
MM
29# We use second-expansion heavily to dynamically compute prerequisites when they
30# are needed. Second-expansion lets us follow make's implicit rule search
31# instead of trying to anticipate which prerequisites it will need in advance.
32.SECONDEXPANSION:
ac64c802 33
8d481cf5
MM
34# TARGET OBFUSCATION
35
36# Mage uses two kinds of "obfuscated targets" (those that are not the simple
37# names of real files):
38# - An always-exists target like /.//. is used as a prerequisite of an implicit
39# rule. Since it exists, make doesn't second-expand its own prerequisites
40# until it is actually run. This way, a target's prerequisites can depend on
41# the results of previous command scripts.
42# - An alternative-name target like /.//./proc/self/cwd/bar is used to check the
43# mtime of a file without actually building it or introducing a circular
44# dependency.
45
46# $(newoid) allocates and returns a new obfuscation ID (oid). Example:
47# x:=$(newoid)
48# You can then refer to target $x or $x$(aname)bar (for any file bar).
49# Prerequisites and commands come from $($x@opr) and $($x@ocmd).
50# Target-specific variables $(oid) and $(otgt) (if alternate-name) are
51# available.
52
53opfx:=/./
54# TODO If the system doesn't support /proc/self/cwd, use something else.
55aname:=/proc/self/cwd/
56nextoid:=/./.
57newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.))))
58
59# High-priority implicit rule for obfuscated targets. Works for both always-
60# exists and alternate-name targets.
61$(opfx)%: oid=$(word 1,$(subst $(aname), ,$@))
62$(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@))
63$(opfx)%: $$($$(oid)@opr)
64 $($(oid)@ocmd)
00804b7c 65
ac64c802
MM
66# MAIN BUILD LOGIC
67
cf5fd926
MM
68# Target metadata variables for bar: (*: stored in bar.g)
69#
70# bar@cmd:=cat foo >bar.tmp
71# Generation command as given to the shell.*
72# bar@warnings:=$(empty)yikes!
73# Stuff the command printed to stdout or stderr.*
74# bar@deps:=included@x oops@
75# If dependency-logging, list of filename@revision used. Revision is x for
76# exists and empty for doesn't exist. Later perhaps x will be the mtime.*
77# bar@gloaded:=1
78# Set if Mage has loaded bar.g and hasn't changed it since then.
79# bar@uptodate:=1
80# Set when Mage makes a file or depends on it being made. Used to determine
81# the next prerequisite to check for a dependency-logging command.
82# bar@generated:=1
83# Set when Mage generates a file; suppresses warning replay.
84#
85# If the rule for bar is overridden, we clear the information from bar.g so that
86# it is as if bar.g didn't exist.
ac64c802
MM
87
88# $(call gload,foo.o)
00804b7c 89# Make sure foo.o's genfile, if any, has been loaded.
ac64c802
MM
90define gload
91$(if $($1@gloaded),,$(eval
92$1@cmd:=
93$1@warnings:=
94$1@deps:=
00804b7c 95-include $1.g
ac64c802 96$1@gloaded:=1
00804b7c
MM
97))
98endef
99
100# $(call mg-translate-cmd,touch $$@)
ac64c802
MM
101# Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary.
102# We won't support $%, $?, $|.
103# There might be false matches, e.g., $$@ => $$(mg@) ;
00804b7c
MM
104# to prevent that, do $$$(empty)@ .
105define mg-translate-cmd
106$(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
107endef
108
30018732 109# $(call mg-define-rule,target,prerequisite,cmd)
00804b7c
MM
110# Defines a rule.
111# If cmd uses $@, quote if necessary so this function sees $@, etc.
112#
ac64c802 113# When we generate bar from foo using a Mage rule:
00804b7c 114# Division of work:
ac64c802
MM
115# - "bar.g: foo" generates bar if necessary.
116# - "bar: bar.g" loads new genfile and replays warnings if bar.g was
117# already up to date.
00804b7c
MM
118# Cases:
119# - bar is managed and up to date => replay
ac64c802 120# - bar doesn't exist or is managed and out of date => generate
00804b7c 121# - bar is unmanaged => warn about override
ac64c802 122MG-FORCE:
ce8fee85 123.PHONY: MG-FORCE
30018732 124mg-scout-oid:=$(newoid)
ac64c802 125define mg-define-rule
00804b7c
MM
126$(eval
127
128# Define some target-specific variables.
129# It might look like we could use $*, but $1 most likely isn't %.
130$1.g: target = $$(@:.g=)
131$1.g: cmd = $(call mg-translate-cmd,$3)
132$1.g: mg@ = $$(target).tmp
30018732
MM
133$1.g: mg^ = $$(filter-out MG-% $$(opfx)%,$$^)
134$1.g: mg+ = $$(filter-out MG-% $$(opfx)%,$$+)
00804b7c
MM
135$1.g: mg< = $$(firstword $$(mg^))
136
ac64c802
MM
137# Rule for the genfile. Evidently all the prerequisites we want second-expanded
138# have to go on the same rule.
139$1.g: $2 $$$$(mg-scout-target) MG-FORCE
140 $$(mg-generate)
141
a62dce77
MM
142$(mg-file-from-genfile)
143)
144endef
145
146define mg-file-from-genfile
ac64c802
MM
147# If the file was regenerated, load the new genfile.
148# If not, replay any warnings.
ac64c802
MM
149# HMMM Maybe errors should go to stderr???
150$1: MG-FORCE | $1.g
151 $$(call gload,$$@)
cf5fd926
MM
152 $$(eval $$@@uptodate:=1)
153 $$(if $$($$@@warnings),$$(if $$($$@@changed),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),)
ac64c802 154endef
00804b7c 155
00804b7c
MM
156# If the target is unmanaged, we must run the rule; we'll see that the
157# obfuscated target is in $? and complain.
30018732 158mg-scout-target=$(if $(wildcard $(target)),$(mg-scout-oid)$(aname)$(target),)
00804b7c
MM
159
160# If the command changed, we must regenerate.
ac64c802 161mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x)
00804b7c
MM
162
163# Just add additional prerequisites.
a62dce77
MM
164# I don't think this can add patterns to implicit rules, but it should be able
165# to add specific prerequisites to uses of implicit rules.
cf5fd926 166# TODO Make this work for dependency-logging commands if desired.
ac64c802 167define mg-define-prereq
00804b7c
MM
168$(eval
169$1.g: $2
170)
171endef
172
ac64c802 173# Procedure to generate bar. Remember, $@ is bar.g.
00804b7c 174# If an override:
ac64c802
MM
175# 1. Complain. (Should we stop "`bar' is up to date" using @:; ?)
176# 2. Clear out the warnings so we don't replay them.
177# Otherwise, check prereqs, command, and nonexistence to decide whether bar
178# needs to be regenerated. If so:
179# 1. Set a flag so Mage knows to reread the genfile when make runs target
180# "bar".
181# 2. Echo the command being run.
182# On error, skip to 8:
183# 3. Open the new genfile bar.g.tmp for writing.
184# 4. Store the command in bar.g.tmp.
185# 5. Run the command to bar.tmp, storing warnings in bar.g.tmp.
186# 6. If bar.tmp differs from bar:
187# a. Clear and touch bar.g (so bar doesn't become an override if we're
188# killed between steps 6b and 7).
189# b. Move in the new bar.
190# 7. Now that bar is known to be up to date, move in the new bar.g.
191# 8. Defensively remove both temporary files and exit with the exit code
192# from before the removal. (trap EXIT)
193define mg-generate
194 $(call gload,$(target))\
30018732 195 $(if $(filter $(mg-scout-oid)$(aname)$(target),$?),\
00804b7c 196 $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\
ac64c802 197 $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\
00804b7c 198 ,\
ac64c802 199 $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\
cf5fd926 200 $(eval $(target)@gloaded:=)$(eval $(target)@changed:=1)$(info $(cmd))\
ac64c802
MM
201 @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\
202 exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\
203 set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
204 $(mg-maybe-move-target) && mv -f $@.tmp $@\
205 ))
00804b7c
MM
206endef
207
ac64c802
MM
208# Pieces of mg-generate that I factored out to make mg-generate more readable.
209mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd)))
210mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; }
211mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/'
212mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); }
213
cf5fd926
MM
214# $(call mg-rule,target,static prerequisites,cmd,[dep converter])
215# Defines a rule with a dependency-logging command.
216# I haven't decided on the format for the dep converter yet.
217dlc-static-run-oid:=$(newoid)
218define mg-define-rule-dlc
219$(eval
220
221# Copied stuff from mg-define-rule to modify as necessary
222
223## Define some target-specific variables.
224## It might look like we could use $*, but $1 most likely isn't %.
225#$1.g: target = $$(@:.g=)
226#$1.g: cmd = $(call mg-translate-cmd,$3)
227#$1.g: mg@ = $$(target).tmp
228#$1.g: mg^ = $$(filter-out MG-% /./%,$$^)
229#$1.g: mg+ = $$(filter-out MG-% /./%,$$+)
230#$1.g: mg< = $$(firstword $$(mg^))
231
232#
233$1.g: MG-FORCE | $$$$(dlc-first-run-oid)$$$$(aname)$1.g $$$$(call dlc-setup-tgt,$(newoid))
234
235## Rule for the genfile. Evidently all the prerequisites we want second-expanded
236## have to go on the same rule.
237#$1.g: $2 $$$$(mg-scout-target) MG-FORCE
238# $$(mg-generate)
239
240
241$(mg-file-from-genfile)
242)
243endef
244
245define dlc-setup-tgt
246$(eval
247
248)$1
249endef
250
ac64c802
MM
251# END
252
00804b7c 253# We don't want anything we defined to become the default goal.
ac64c802 254.DEFAULT_GOAL := $(mg-orig-default-goal)