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) | |
cf5fd926 MM |
108 | # |
109 | # bar@cmd:=cat foo >bar.tmp | |
110 | # Generation command as given to the shell.* | |
ff59f3cf | 111 | # |
cf5fd926 | 112 | # bar@warnings:=$(empty)yikes! |
15cf220e | 113 | # Data that the command printed to stdout or stderr, presumably warnings.* |
ff59f3cf | 114 | # |
cf5fd926 MM |
115 | # bar@deps:=included@x oops@ |
116 | # If dependency-logging, list of filename@revision used. Revision is x for | |
117 | # exists and empty for doesn't exist. Later perhaps x will be the mtime.* | |
ff59f3cf | 118 | # |
cf5fd926 | 119 | # bar@gloaded:=1 |
099638eb | 120 | # Set if mgear has loaded bar.g and hasn't changed it since then. |
ff59f3cf MM |
121 | # |
122 | # bar@gdeps:=foo | |
123 | # Static dependencies of bar, for checking by bar.g. | |
124 | # | |
125 | # bar@gq:=foo $(mg-scout-oid)$(aname)bar | |
126 | # $? from the rule for bar.g; used by the rule for bar. | |
127 | # | |
15cf220e | 128 | # bar@checked:=1 |
099638eb | 129 | # Set when mgear determines that a file is up to date or depends on it being |
15cf220e MM |
130 | # so determined. Used to decide which prerequisite to check next for a |
131 | # dependency-logging command. | |
132 | # | |
133 | # bar@dlc-ran:=1 | |
099638eb | 134 | # Indicates that mgear is in the middle of building bar using a dependency- |
15cf220e MM |
135 | # logging command. Means that bar.g.tmp, not bar.g, is the most current |
136 | # genfile. | |
cf5fd926 | 137 | # |
ff59f3cf MM |
138 | ## If the rule for bar is overridden, we clear the information from bar.g so |
139 | ## that it is as if bar.g didn't exist. -- Not currently needed | |
140 | # | |
099638eb | 141 | # Some make features with which mgear's compatibility has not been investigated: |
ff59f3cf MM |
142 | # - Command-line options (especially --dry-run, --question, --touch, |
143 | # --always-make, --keep-going, --jobs, --assume-old, and --assume-new) | |
144 | # - Static pattern rules | |
145 | # - Vpath | |
ac64c802 MM |
146 | |
147 | # $(call gload,foo.o) | |
00804b7c | 148 | # Make sure foo.o's genfile, if any, has been loaded. |
ac64c802 MM |
149 | define gload |
150 | $(if $($1@gloaded),,$(eval | |
151 | $1@cmd:= | |
152 | $1@warnings:= | |
153 | $1@deps:= | |
00804b7c | 154 | -include $1.g |
ac64c802 | 155 | $1@gloaded:=1 |
00804b7c MM |
156 | )) |
157 | endef | |
158 | ||
ff59f3cf MM |
159 | # bar.g: scout bar and its dependencies and store $?. When implicit rules |
160 | # compete for bar, we depend on the rule make uses being the last one it | |
161 | # second-expands so that $(bar@gdeps) is still correct. | |
162 | mg-genfile-oid:=$(newoid) | |
163 | mg-scout-oid:=$(newoid) | |
164 | $(mg-genfile-oid)@opr=$($(otgt:.g=)@gdeps) $(if $(wildcard $(otgt:.g=)),$(mg-scout-oid)$(aname)$(otgt:.g=),) | |
165 | $(mg-genfile-oid)@ocmd=$(eval $(otgt:.g=)@gq:=$?) | |
166 | ||
099638eb | 167 | # Mgear-ized automatic variables. |
ff59f3cf MM |
168 | # $@: needs no translation |
169 | # NOTE: $(mg@) is *eventual* target. Commands must write to temp file, $t . | |
170 | # $%: haven't thought about it much, but probably needs no translation | |
171 | mg< = $(firstword $(mg^)) | |
172 | mg? = $(filter-out $(opfx)%,$($@@gq)) | |
173 | mg^ = $(filter-out MG-% $(opfx)%,$^) | |
174 | mg+ = $(filter-out MG-% $(opfx)%,$+) | |
175 | # $|: needs no translation | |
176 | # $*: needs no translation | |
177 | ||
178 | # $(call mg-translate-cmd,cat $$< >$$t) | |
099638eb | 179 | # Replaces references to automatic variables with references to their mgear-ized |
ff59f3cf MM |
180 | # counterparts. There might be false matches, e.g., $$@ => $$(mg@) ; |
181 | # to prevent that, write $$$(empty)@ instead. (c.f. autoconf empty quadrigraph) | |
21a50371 | 182 | mg-translate-cmd=$(subst $$t,$$@.tmp,$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))) |
00804b7c | 183 | |
21a50371 | 184 | # $(call mg-prereq-predict,target,prerequisites) |
ff59f3cf MM |
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. | |
21a50371 MM |
193 | mg-set-gdeps=$(eval $@@gdeps:=$1) |
194 | mg-prereq-predict=$$$$(call mg-set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2)))) | |
ff59f3cf MM |
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 | |
099638eb | 203 | # Mgear-ized automatic variables are available. |
21a50371 MM |
204 | # |
205 | # I eradicated the target-specific variables because they fail when there are | |
206 | # multiple implicit rules with the same target pattern. | |
207 | # | |
ff59f3cf MM |
208 | # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new |
209 | # prerequisites. The command script always runs. Depend on bar.g to get | |
210 | # $(bar@gq). | |
21a50371 MM |
211 | define mg-define-rule |
212 | $(eval $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g | |
213 | $$(call mg-rule-cmd,$(call mg-translate-cmd,$3))) | |
00804b7c MM |
214 | endef |
215 | ||
ff59f3cf MM |
216 | # TODO Provide a way to define static pattern rules. |
217 | ||
21a50371 | 218 | # Procedure to generate bar. Now $@ is bar, so we say $@.g for bar.g. |
00804b7c | 219 | # If an override: |
ac64c802 | 220 | # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) |
21a50371 | 221 | # 2. This would be the place to clear bar's metadata if we wanted to. |
ac64c802 MM |
222 | # Otherwise, check prereqs, command, and nonexistence to decide whether bar |
223 | # needs to be regenerated. If so: | |
21a50371 | 224 | # 1. Note that the genfile is about to change. |
ac64c802 MM |
225 | # 2. Echo the command being run. |
226 | # On error, skip to 8: | |
227 | # 3. Open the new genfile bar.g.tmp for writing. | |
21a50371 MM |
228 | # 4. Run the command to bar.tmp, storing warnings in bar.g.tmp. |
229 | # 5. Store the command in bar.g.tmp. Do it here to ensure that bar.g.tmp | |
230 | # is at least as new as bar.tmp. | |
ac64c802 MM |
231 | # 6. If bar.tmp differs from bar: |
232 | # a. Clear and touch bar.g (so bar doesn't become an override if we're | |
233 | # killed between steps 6b and 7). | |
234 | # b. Move in the new bar. | |
235 | # 7. Now that bar is known to be up to date, move in the new bar.g. | |
236 | # 8. Defensively remove both temporary files and exit with the exit code | |
237 | # from before the removal. (trap EXIT) | |
ff59f3cf MM |
238 | # If not: |
239 | # 1. If there were warnings, replay them. (HMMM To stderr?) | |
240 | # HMMM .tmp in displayed command looks ugly | |
241 | define mg-rule-cmd | |
242 | $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\ | |
099638eb | 243 | $(info mgear: warning: Manually created/modified file at $@ overrides rule.)\ |
21a50371 MM |
244 | ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,TARGET-DNE)$(mg-check-cmd),\ |
245 | $(eval $@@gloaded:=)$(info $1)\ | |
ff59f3cf | 246 | @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\ |
21a50371 | 247 | exec 3>$@.g.tmp &&\ |
ff59f3cf | 248 | set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ |
21a50371 MM |
249 | $(mg-assign-cmd) >&3 &&\ |
250 | $(mg-maybe-move-target) &&\ | |
251 | mv -f $@.g.tmp $@.g\ | |
ff59f3cf | 252 | ,$(if $($@@warnings),\ |
099638eb | 253 | $(info $($@@cmd) # mgear warning replay$(nl)$($@@warnings))\ |
ff59f3cf | 254 | ))) |
cf5fd926 MM |
255 | endef |
256 | ||
ff59f3cf | 257 | # If the command changed, we must regenerate. |
21a50371 | 258 | mg-check-cmd=$(if $(call streq,$1,$($@@cmd)),,COMMAND-CHANGED) |
cf5fd926 | 259 | |
ff59f3cf | 260 | # Pieces of mg-generate that I factored out to make mg-generate more readable. |
21a50371 | 261 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$1)) |
099638eb | 262 | mg-run-cmd={ ($1) 2>&1 && { [ -r $@.tmp ] || { echo 'mgear: error: Command for $@ succeeded without creating it!'; false; }; }; } |
ff59f3cf | 263 | mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/' |
21a50371 MM |
264 | # Drat bash's lack of precedence between || and &&. Extra braces necessary. |
265 | mg-maybe-move-target={ cmp -s $@ $@.tmp || { echo >$@.g && mv -f $@.tmp $@; }; } | |
ff59f3cf MM |
266 | |
267 | # Just add additional prerequisites. This cannot add prerequisite patterns to | |
268 | # an implicit rule, but it can add specific prerequisites to an individual use | |
099638eb MM |
269 | # of an implicit rule. Currently, mgear picks up the target's prerequisites |
270 | # from make, so this just attaches the given prerequisites to the target, but | |
271 | # the implementation might change in the future. | |
21a50371 MM |
272 | mg-define-prereq=$(eval $1: $2) |
273 | ||
274 | ||
275 | # DEPENDENCY-LOGGING COMMANDS | |
276 | ||
277 | mg-dlc-static-run-oid:=$(mg-genfile-oid) | |
278 | ||
279 | # $(call mg-define-rule-dlc,target,static-prerequisites,cmd,dep-converter) | |
280 | # Analogue of mg-define-rule for a dependency-logging command. | |
281 | # I haven't decided on the format for the dep-converter yet. | |
282 | define mg-define-rule-dlc | |
283 | $(eval | |
284 | ||
285 | # FINISH | |
286 | ||
287 | # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new | |
288 | # prerequisites. The command script always runs. Finally, do the static run | |
289 | # and then the first dynamic check. | |
290 | $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-dlc-static-run-oid)$(aname)$$$$@.g $$$$(call dlc-next-dchk,$$$$(target)) | |
291 | # TODO: Move the finished file into place or something | |
292 | # $$(call mg-rule-cmd,$3) | |
293 | ) | |
cf5fd926 MM |
294 | endef |
295 | ||
21a50371 MM |
296 | #mg-dlc-next-dchk=$(call mg-dlc-next-dchk-1,$1,$(newoid)) |
297 | #mg-dlc-next-dchk-1=$(eval $2@opr=$$(call mg-dlc-dchk-pr,$1,$2))$2 | |
298 | # | |
299 | ## $(call mg-dlc-dchk-pr,target,oid) | |
300 | #mg-dlc-dchk-pr=$(call mg-dlc-dchk-pr-1,$1,$2,$(call next-unchecked-prereq,$($1@deps))) | |
301 | ## $(call mg-next-unchecked-prereq,foo.c bar.h baz.h) | |
302 | #mg-next-unchecked-prereq=$(firstword $(foreach p,$1,$(if $($p@checked),,$p))) | |
303 | #define mg-dlc-dchk-pr-1 | |
304 | #$(if $3,$(call mg-dlc-drun,$1,$2,$3) $(call mg-dlc-next-dchk,$1),) | |
305 | #endef | |
306 | # | |
307 | ## $(call mg-dlc-drun,target,oid,prereq). FINISH | |
308 | #define mg-dlc-drun | |
309 | #$(eval | |
310 | #$2$(aname)$3@opr:=$3 | |
311 | #$2$(aname)$3@ocmd:= | |
312 | #)$2$(aname)$3 | |
313 | #endef | |
314 | # | |
315 | # | |
316 | ## END | |
ac64c802 | 317 | |
00804b7c | 318 | # We don't want anything we defined to become the default goal. |
ac64c802 | 319 | .DEFAULT_GOAL := $(mg-orig-default-goal) |