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 | ||
ac64c802 MM |
13 | |
14 | # TEXT UTILITIES | |
ff59f3cf MM |
15 | empty:= |
16 | bs:=\$(empty) | |
21a50371 | 17 | hash:=\# |
ff59f3cf | 18 | pct:=% |
00804b7c MM |
19 | define nl |
20 | ||
21 | ||
22 | endef | |
23 | # Shell-quote: a'b => 'a'\''b' | |
ff59f3cf | 24 | sq='$(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 | 27 | mqas=$(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 | 30 | streq=$(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 | 36 | fmt-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 | ||
58 | opfx:=/./ | |
59 | # TODO If the system doesn't support /proc/self/cwd, use something else. | |
60 | aname:=/proc/self/cwd/ | |
61 | nextoid:=/./. | |
62 | newoid=$(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 | 150 | define 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 | )) |
160 | endef | |
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. | |
166 | mg-genfile-oid:=$(newoid) | |
167 | mg-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 | |
177 | mg< = $(firstword $(mg^)) | |
178 | mg? = $(filter-out $(opfx)%,$($@@gq)) | |
179 | mg^ = $(filter-out MG-% $(opfx)%,$^) | |
180 | mg+ = $(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 | 188 | mg-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 |
199 | mg-set-gdeps=$(eval $@@gdeps:=$1) |
200 | mg-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 | 204 | MG-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 |
217 | define 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 |
220 | endef |
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 | |
247 | define 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 |
261 | endef |
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. | |
266 | mg-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 | 269 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,@cmd,$1)) |
099638eb | 270 | mg-run-cmd={ ($1) 2>&1 && { [ -r $@.tmp ] || { echo 'mgear: error: Command for $@ succeeded without creating it!'; false; }; }; } |
eeabaff8 | 271 | mg-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. |
273 | mg-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 |
280 | mg-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 | ||
295 | define 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 | |
304 | endef | |
305 | ||
306 | ||
21a50371 MM |
307 | # DEPENDENCY-LOGGING COMMANDS |
308 | ||
309 | mg-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. | |
314 | define 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 |
326 | endef |
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) |