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