Commit | Line | Data |
---|---|---|
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. |
5 | mg-orig-default-goal := $(.DEFAULT_GOAL) | |
6 | ||
7 | # TEXT UTILITIES | |
00804b7c MM |
8 | empty := |
9 | bs := \$(empty) | |
10 | define nl | |
11 | ||
12 | ||
13 | endef | |
14 | # Shell-quote: a'b => 'a'\''b' | |
15 | sq = '$(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!* | |
18 | mqas = $(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). | |
21 | streq = $(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. | |
27 | fmt-make-assignment = $1:=$$(empty)$(call mqas,$2) | |
28 | ||
8d481cf5 MM |
29 | # We use second-expansion heavily to dynamically compute prerequisites when they |
30 | # are needed. Second-expansion lets us follow make's implicit rule search | |
31 | # instead of trying to anticipate which prerequisites it will need in advance. | |
32 | .SECONDEXPANSION: | |
ac64c802 | 33 | |
8d481cf5 MM |
34 | # TARGET OBFUSCATION |
35 | ||
36 | # Mage uses two kinds of "obfuscated targets" (those that are not the simple | |
37 | # names of real files): | |
38 | # - An always-exists target like /.//. is used as a prerequisite of an implicit | |
39 | # rule. Since it exists, make doesn't second-expand its own prerequisites | |
40 | # until it is actually run. This way, a target's prerequisites can depend on | |
41 | # the results of previous command scripts. | |
42 | # - An alternative-name target like /.//./proc/self/cwd/bar is used to check the | |
43 | # mtime of a file without actually building it or introducing a circular | |
44 | # dependency. | |
45 | ||
46 | # $(newoid) allocates and returns a new obfuscation ID (oid). Example: | |
47 | # x:=$(newoid) | |
48 | # You can then refer to target $x or $x$(aname)bar (for any file bar). | |
49 | # Prerequisites and commands come from $($x@opr) and $($x@ocmd). | |
50 | # Target-specific variables $(oid) and $(otgt) (if alternate-name) are | |
51 | # available. | |
52 | ||
53 | opfx:=/./ | |
54 | # TODO If the system doesn't support /proc/self/cwd, use something else. | |
55 | aname:=/proc/self/cwd/ | |
56 | nextoid:=/./. | |
57 | newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.)))) | |
58 | ||
59 | # High-priority implicit rule for obfuscated targets. Works for both always- | |
60 | # exists and alternate-name targets. | |
61 | $(opfx)%: oid=$(word 1,$(subst $(aname), ,$@)) | |
62 | $(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@)) | |
63 | $(opfx)%: $$($$(oid)@opr) | |
64 | $($(oid)@ocmd) | |
00804b7c | 65 | |
ac64c802 MM |
66 | # MAIN BUILD LOGIC |
67 | ||
cf5fd926 MM |
68 | # Target metadata variables for bar: (*: stored in bar.g) |
69 | # | |
70 | # bar@cmd:=cat foo >bar.tmp | |
71 | # Generation command as given to the shell.* | |
72 | # bar@warnings:=$(empty)yikes! | |
73 | # Stuff the command printed to stdout or stderr.* | |
74 | # bar@deps:=included@x oops@ | |
75 | # If dependency-logging, list of filename@revision used. Revision is x for | |
76 | # exists and empty for doesn't exist. Later perhaps x will be the mtime.* | |
77 | # bar@gloaded:=1 | |
78 | # Set if Mage has loaded bar.g and hasn't changed it since then. | |
79 | # bar@uptodate:=1 | |
80 | # Set when Mage makes a file or depends on it being made. Used to determine | |
81 | # the next prerequisite to check for a dependency-logging command. | |
82 | # bar@generated:=1 | |
83 | # Set when Mage generates a file; suppresses warning replay. | |
84 | # | |
85 | # If the rule for bar is overridden, we clear the information from bar.g so that | |
86 | # it is as if bar.g didn't exist. | |
ac64c802 MM |
87 | |
88 | # $(call gload,foo.o) | |
00804b7c | 89 | # Make sure foo.o's genfile, if any, has been loaded. |
ac64c802 MM |
90 | define gload |
91 | $(if $($1@gloaded),,$(eval | |
92 | $1@cmd:= | |
93 | $1@warnings:= | |
94 | $1@deps:= | |
00804b7c | 95 | -include $1.g |
ac64c802 | 96 | $1@gloaded:=1 |
00804b7c MM |
97 | )) |
98 | endef | |
99 | ||
100 | # $(call mg-translate-cmd,touch $$@) | |
ac64c802 MM |
101 | # Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary. |
102 | # We won't support $%, $?, $|. | |
103 | # There might be false matches, e.g., $$@ => $$(mg@) ; | |
00804b7c MM |
104 | # to prevent that, do $$$(empty)@ . |
105 | define mg-translate-cmd | |
106 | $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) | |
107 | endef | |
108 | ||
30018732 | 109 | # $(call mg-define-rule,target,prerequisite,cmd) |
00804b7c MM |
110 | # Defines a rule. |
111 | # If cmd uses $@, quote if necessary so this function sees $@, etc. | |
112 | # | |
ac64c802 | 113 | # When we generate bar from foo using a Mage rule: |
00804b7c | 114 | # Division of work: |
ac64c802 MM |
115 | # - "bar.g: foo" generates bar if necessary. |
116 | # - "bar: bar.g" loads new genfile and replays warnings if bar.g was | |
117 | # already up to date. | |
00804b7c MM |
118 | # Cases: |
119 | # - bar is managed and up to date => replay | |
ac64c802 | 120 | # - bar doesn't exist or is managed and out of date => generate |
00804b7c | 121 | # - bar is unmanaged => warn about override |
ac64c802 | 122 | MG-FORCE: |
ce8fee85 | 123 | .PHONY: MG-FORCE |
30018732 | 124 | mg-scout-oid:=$(newoid) |
ac64c802 | 125 | define mg-define-rule |
00804b7c MM |
126 | $(eval |
127 | ||
128 | # Define some target-specific variables. | |
129 | # It might look like we could use $*, but $1 most likely isn't %. | |
130 | $1.g: target = $$(@:.g=) | |
131 | $1.g: cmd = $(call mg-translate-cmd,$3) | |
132 | $1.g: mg@ = $$(target).tmp | |
30018732 MM |
133 | $1.g: mg^ = $$(filter-out MG-% $$(opfx)%,$$^) |
134 | $1.g: mg+ = $$(filter-out MG-% $$(opfx)%,$$+) | |
00804b7c MM |
135 | $1.g: mg< = $$(firstword $$(mg^)) |
136 | ||
ac64c802 MM |
137 | # Rule for the genfile. Evidently all the prerequisites we want second-expanded |
138 | # have to go on the same rule. | |
139 | $1.g: $2 $$$$(mg-scout-target) MG-FORCE | |
140 | $$(mg-generate) | |
141 | ||
a62dce77 MM |
142 | $(mg-file-from-genfile) |
143 | ) | |
144 | endef | |
145 | ||
146 | define mg-file-from-genfile | |
ac64c802 MM |
147 | # If the file was regenerated, load the new genfile. |
148 | # If not, replay any warnings. | |
ac64c802 MM |
149 | # HMMM Maybe errors should go to stderr??? |
150 | $1: MG-FORCE | $1.g | |
151 | $$(call gload,$$@) | |
cf5fd926 MM |
152 | $$(eval $$@@uptodate:=1) |
153 | $$(if $$($$@@warnings),$$(if $$($$@@changed),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),) | |
ac64c802 | 154 | endef |
00804b7c | 155 | |
00804b7c MM |
156 | # If the target is unmanaged, we must run the rule; we'll see that the |
157 | # obfuscated target is in $? and complain. | |
30018732 | 158 | mg-scout-target=$(if $(wildcard $(target)),$(mg-scout-oid)$(aname)$(target),) |
00804b7c MM |
159 | |
160 | # If the command changed, we must regenerate. | |
ac64c802 | 161 | mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x) |
00804b7c MM |
162 | |
163 | # Just add additional prerequisites. | |
a62dce77 MM |
164 | # I don't think this can add patterns to implicit rules, but it should be able |
165 | # to add specific prerequisites to uses of implicit rules. | |
cf5fd926 | 166 | # TODO Make this work for dependency-logging commands if desired. |
ac64c802 | 167 | define mg-define-prereq |
00804b7c MM |
168 | $(eval |
169 | $1.g: $2 | |
170 | ) | |
171 | endef | |
172 | ||
ac64c802 | 173 | # Procedure to generate bar. Remember, $@ is bar.g. |
00804b7c | 174 | # If an override: |
ac64c802 MM |
175 | # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) |
176 | # 2. Clear out the warnings so we don't replay them. | |
177 | # Otherwise, check prereqs, command, and nonexistence to decide whether bar | |
178 | # needs to be regenerated. If so: | |
179 | # 1. Set a flag so Mage knows to reread the genfile when make runs target | |
180 | # "bar". | |
181 | # 2. Echo the command being run. | |
182 | # On error, skip to 8: | |
183 | # 3. Open the new genfile bar.g.tmp for writing. | |
184 | # 4. Store the command in bar.g.tmp. | |
185 | # 5. Run the command to bar.tmp, storing warnings in bar.g.tmp. | |
186 | # 6. If bar.tmp differs from bar: | |
187 | # a. Clear and touch bar.g (so bar doesn't become an override if we're | |
188 | # killed between steps 6b and 7). | |
189 | # b. Move in the new bar. | |
190 | # 7. Now that bar is known to be up to date, move in the new bar.g. | |
191 | # 8. Defensively remove both temporary files and exit with the exit code | |
192 | # from before the removal. (trap EXIT) | |
193 | define mg-generate | |
194 | $(call gload,$(target))\ | |
30018732 | 195 | $(if $(filter $(mg-scout-oid)$(aname)$(target),$?),\ |
00804b7c | 196 | $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\ |
ac64c802 | 197 | $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\ |
00804b7c | 198 | ,\ |
ac64c802 | 199 | $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\ |
cf5fd926 | 200 | $(eval $(target)@gloaded:=)$(eval $(target)@changed:=1)$(info $(cmd))\ |
ac64c802 MM |
201 | @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\ |
202 | exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\ | |
203 | set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ | |
204 | $(mg-maybe-move-target) && mv -f $@.tmp $@\ | |
205 | )) | |
00804b7c MM |
206 | endef |
207 | ||
ac64c802 MM |
208 | # Pieces of mg-generate that I factored out to make mg-generate more readable. |
209 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd))) | |
210 | mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } | |
211 | mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/' | |
212 | mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); } | |
213 | ||
cf5fd926 MM |
214 | # $(call mg-rule,target,static prerequisites,cmd,[dep converter]) |
215 | # Defines a rule with a dependency-logging command. | |
216 | # I haven't decided on the format for the dep converter yet. | |
217 | dlc-static-run-oid:=$(newoid) | |
218 | define mg-define-rule-dlc | |
219 | $(eval | |
220 | ||
221 | # Copied stuff from mg-define-rule to modify as necessary | |
222 | ||
223 | ## Define some target-specific variables. | |
224 | ## It might look like we could use $*, but $1 most likely isn't %. | |
225 | #$1.g: target = $$(@:.g=) | |
226 | #$1.g: cmd = $(call mg-translate-cmd,$3) | |
227 | #$1.g: mg@ = $$(target).tmp | |
228 | #$1.g: mg^ = $$(filter-out MG-% /./%,$$^) | |
229 | #$1.g: mg+ = $$(filter-out MG-% /./%,$$+) | |
230 | #$1.g: mg< = $$(firstword $$(mg^)) | |
231 | ||
232 | # | |
233 | $1.g: MG-FORCE | $$$$(dlc-first-run-oid)$$$$(aname)$1.g $$$$(call dlc-setup-tgt,$(newoid)) | |
234 | ||
235 | ## Rule for the genfile. Evidently all the prerequisites we want second-expanded | |
236 | ## have to go on the same rule. | |
237 | #$1.g: $2 $$$$(mg-scout-target) MG-FORCE | |
238 | # $$(mg-generate) | |
239 | ||
240 | ||
241 | $(mg-file-from-genfile) | |
242 | ) | |
243 | endef | |
244 | ||
245 | define dlc-setup-tgt | |
246 | $(eval | |
247 | ||
248 | )$1 | |
249 | endef | |
250 | ||
ac64c802 MM |
251 | # END |
252 | ||
00804b7c | 253 | # We don't want anything we defined to become the default goal. |
ac64c802 | 254 | .DEFAULT_GOAL := $(mg-orig-default-goal) |