- Revise and comment target metadata variables.
[mgear/mgear.git] / src / mage.mk
1 # Mage Build Tool by Matt McCutchen
2 # http://www.kepreon.com/~matt/mage/
3
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
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
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:
33
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)
65
66 # MAIN BUILD LOGIC
67
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.
87
88 # $(call gload,foo.o)
89 # Make sure foo.o's genfile, if any, has been loaded.
90 define gload
91 $(if $($1@gloaded),,$(eval 
92 $1@cmd:=
93 $1@warnings:=
94 $1@deps:=
95 -include $1.g
96 $1@gloaded:=1
97 ))
98 endef
99
100 # $(call mg-translate-cmd,touch $$@)
101 # Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary.
102 # We won't support $%, $?, $|.
103 # There might be false matches, e.g., $$@ => $$(mg@) ;
104 # to prevent that, do $$$(empty)@ .
105 define mg-translate-cmd
106 $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
107 endef
108
109 # $(call mg-define-rule,target,prerequisite,cmd)
110 # Defines a rule.
111 # If cmd uses $@, quote if necessary so this function sees $@, etc.
112
113 # When we generate bar from foo using a Mage rule:
114 # Division of work:
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.
118 # Cases:
119 # - bar is managed and up to date => replay
120 # - bar doesn't exist or is managed and out of date => generate
121 # - bar is unmanaged => warn about override 
122 MG-FORCE:
123 .PHONY: MG-FORCE
124 mg-scout-oid:=$(newoid)
125 define mg-define-rule
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
133 $1.g: mg^ = $$(filter-out MG-% $$(opfx)%,$$^)
134 $1.g: mg+ = $$(filter-out MG-% $$(opfx)%,$$+)
135 $1.g: mg< = $$(firstword $$(mg^))
136
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
142 $(mg-file-from-genfile)
143 )
144 endef
145
146 define mg-file-from-genfile
147 # If the file was regenerated, load the new genfile.
148 # If not, replay any warnings.
149 # HMMM Maybe errors should go to stderr???
150 $1: MG-FORCE | $1.g
151         $$(call gload,$$@)
152         $$(eval $$@@uptodate:=1)
153         $$(if $$($$@@warnings),$$(if $$($$@@changed),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),)
154 endef
155
156 # If the target is unmanaged, we must run the rule; we'll see that the
157 # obfuscated target is in $? and complain.
158 mg-scout-target=$(if $(wildcard $(target)),$(mg-scout-oid)$(aname)$(target),)
159
160 # If the command changed, we must regenerate.
161 mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x)
162
163 # Just add additional prerequisites.
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.
166 # TODO Make this work for dependency-logging commands if desired.
167 define mg-define-prereq
168 $(eval 
169 $1.g: $2
170 )
171 endef
172
173 # Procedure to generate bar.  Remember, $@ is bar.g.
174 # If an override:
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))\
195         $(if $(filter $(mg-scout-oid)$(aname)$(target),$?),\
196                 $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\
197                 $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\
198         ,\
199         $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\
200         $(eval $(target)@gloaded:=)$(eval $(target)@changed:=1)$(info $(cmd))\
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         ))
206 endef
207
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
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
251 # END
252
253 # We don't want anything we defined to become the default goal.
254 .DEFAULT_GOAL := $(mg-orig-default-goal)