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