We don't actually need MG-FORCE-TARGET-DNE any more.
[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
ac64c802 29# OBFUSCATION
00804b7c 30
ac64c802
MM
31# Coming soon.
32
33# High-priority implicit rule to ensure that we don't try to actually update
34# obfuscated targets. TEMP
35/./proc/self/cwd/%:
36
00804b7c 37
ac64c802
MM
38# MAIN BUILD LOGIC
39
40# bar.g file format:
41# bar@cmd:=cat foo >bar.tmp
42# bar@warnings:=$(empty)yikes!
43# And if dependency-logging:
44# List of filename@revision, revision is x for exists and empty for doesn't
45# exist. Later perhaps x will be the mtime.
46# bar@deps:=included@x oops@
47# If bar is overridden, we clear all three variables.
48
49# $(call gload,foo.o)
00804b7c 50# Make sure foo.o's genfile, if any, has been loaded.
ac64c802
MM
51define gload
52$(if $($1@gloaded),,$(eval
53$1@cmd:=
54$1@warnings:=
55$1@deps:=
00804b7c 56-include $1.g
ac64c802 57$1@gloaded:=1
00804b7c
MM
58))
59endef
60
61# $(call mg-translate-cmd,touch $$@)
ac64c802
MM
62# Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary.
63# We won't support $%, $?, $|.
64# There might be false matches, e.g., $$@ => $$(mg@) ;
00804b7c
MM
65# to prevent that, do $$$(empty)@ .
66define mg-translate-cmd
67$(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
68endef
69
70# OOOH!!! .SECONDEXPANSION does let us watch the implicit rule search as it
71# happens.
72.SECONDEXPANSION:
73
74# $(call mg-rule,target,prerequisite,cmd)
75# Defines a rule.
76# If cmd uses $@, quote if necessary so this function sees $@, etc.
77#
ac64c802 78# When we generate bar from foo using a Mage rule:
00804b7c 79# Division of work:
ac64c802
MM
80# - "bar.g: foo" generates bar if necessary.
81# - "bar: bar.g" loads new genfile and replays warnings if bar.g was
82# already up to date.
00804b7c
MM
83# Cases:
84# - bar is managed and up to date => replay
ac64c802 85# - bar doesn't exist or is managed and out of date => generate
00804b7c 86# - bar is unmanaged => warn about override
ac64c802 87MG-FORCE:
ce8fee85 88.PHONY: MG-FORCE
ac64c802 89define mg-define-rule
00804b7c
MM
90$(eval
91
92# Define some target-specific variables.
93# It might look like we could use $*, but $1 most likely isn't %.
94$1.g: target = $$(@:.g=)
95$1.g: cmd = $(call mg-translate-cmd,$3)
96$1.g: mg@ = $$(target).tmp
ac64c802
MM
97$1.g: mg^ = $$(filter-out MG-% /./%,$$^)
98$1.g: mg+ = $$(filter-out MG-% /./%,$$+)
00804b7c
MM
99$1.g: mg< = $$(firstword $$(mg^))
100
ac64c802
MM
101# Rule for the genfile. Evidently all the prerequisites we want second-expanded
102# have to go on the same rule.
103$1.g: $2 $$$$(mg-scout-target) MG-FORCE
104 $$(mg-generate)
105
106# If the file was regenerated, load the new genfile.
107# If not, replay any warnings.
108## We don't have to worry about .DELETE_ON_ERROR deleting the target because
109## it is only touched if we *successfully* regenerate it.
110# - No longer applies because we don't remember errors.
111# HMMM Maybe errors should go to stderr???
112$1: MG-FORCE | $1.g
113 $$(call gload,$$@)
114 $$(if $$($$@@warnings),$$(if $$($$@@built),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),)
115)
116endef
00804b7c 117
00804b7c
MM
118# If the target is unmanaged, we must run the rule; we'll see that the
119# obfuscated target is in $? and complain.
ac64c802 120mg-scout-target=$(if $(wildcard $(target)),/./proc/self/cwd/$(target),)
00804b7c
MM
121
122# If the command changed, we must regenerate.
ac64c802 123mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x)
00804b7c
MM
124
125# Just add additional prerequisites.
ac64c802
MM
126# I don't think this works for implicit rules.
127define mg-define-prereq
00804b7c
MM
128$(eval
129$1.g: $2
130)
131endef
132
ac64c802 133# Procedure to generate bar. Remember, $@ is bar.g.
00804b7c 134# If an override:
ac64c802
MM
135# 1. Complain. (Should we stop "`bar' is up to date" using @:; ?)
136# 2. Clear out the warnings so we don't replay them.
137# Otherwise, check prereqs, command, and nonexistence to decide whether bar
138# needs to be regenerated. If so:
139# 1. Set a flag so Mage knows to reread the genfile when make runs target
140# "bar".
141# 2. Echo the command being run.
142# On error, skip to 8:
143# 3. Open the new genfile bar.g.tmp for writing.
144# 4. Store the command in bar.g.tmp.
145# 5. Run the command to bar.tmp, storing warnings in bar.g.tmp.
146# 6. If bar.tmp differs from bar:
147# a. Clear and touch bar.g (so bar doesn't become an override if we're
148# killed between steps 6b and 7).
149# b. Move in the new bar.
150# 7. Now that bar is known to be up to date, move in the new bar.g.
151# 8. Defensively remove both temporary files and exit with the exit code
152# from before the removal. (trap EXIT)
153define mg-generate
154 $(call gload,$(target))\
155 $(if $(filter /./proc/self/cwd/$(target),$?),\
00804b7c 156 $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\
ac64c802 157 $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\
00804b7c 158 ,\
ac64c802
MM
159 $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\
160 $(eval $(target)@gloaded:=)$(eval $(target)@built:=1)$(info $(cmd))\
161 @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\
162 exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\
163 set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
164 $(mg-maybe-move-target) && mv -f $@.tmp $@\
165 ))
00804b7c
MM
166endef
167
ac64c802
MM
168# Pieces of mg-generate that I factored out to make mg-generate more readable.
169mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd)))
170mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; }
171mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/'
172mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); }
173
174# END
175
00804b7c 176# We don't want anything we defined to become the default goal.
ac64c802 177.DEFAULT_GOAL := $(mg-orig-default-goal)