Commit | Line | Data |
---|---|---|
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 |
5 | mg-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 |
14 | empty:= |
15 | bs:=\$(empty) | |
16 | pct:=% | |
00804b7c MM |
17 | define nl |
18 | ||
19 | ||
20 | endef | |
21 | # Shell-quote: a'b => 'a'\''b' | |
ff59f3cf | 22 | sq='$(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 | 25 | mqas=$(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 | 28 | streq=$(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 | 34 | fmt-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 | ||
56 | opfx:=/./ | |
57 | # TODO If the system doesn't support /proc/self/cwd, use something else. | |
58 | aname:=/proc/self/cwd/ | |
59 | nextoid:=/./. | |
60 | newoid=$(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 |
147 | define 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 | )) |
155 | endef | |
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. | |
160 | mg-genfile-oid:=$(newoid) | |
161 | mg-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 | |
169 | mg< = $(firstword $(mg^)) | |
170 | mg? = $(filter-out $(opfx)%,$($@@gq)) | |
171 | mg^ = $(filter-out MG-% $(opfx)%,$^) | |
172 | mg+ = $(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 | 180 | define mg-translate-cmd |
ff59f3cf | 181 | $(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) |
00804b7c MM |
182 | endef |
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. | |
193 | set-gdeps=$(eval $@@gdeps:=$1) | |
194 | prereq-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 | 198 | MG-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 | 204 | define 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 | ) |
218 | endef | |
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 | |
245 | define 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 |
258 | endef |
259 | ||
ff59f3cf MM |
260 | # If the command changed, we must regenerate. |
261 | mg-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. |
264 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$(cmd))) | |
265 | mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $@.tmp ] || { echo 'Mage: error: Command for $@ succeeded without creating it!'; false; }; }; } | |
266 | mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/' | |
267 | mg-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. | |
274 | define mg-define-prereq | |
275 | $(eval $1: $2) | |
cf5fd926 MM |
276 | endef |
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) |