Rewrite the Mage basics, temporarily removing the dependency-logging command
[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 # We use second-expansion heavily to dynamically compute prerequisites when they
8 # are needed.  Second-expansion lets us follow make's implicit rule search
9 # instead of trying to anticipate which prerequisites it will need in advance.
10 .SECONDEXPANSION:
11
12
13 # TEXT UTILITIES
14 empty:=
15 bs:=\$(empty)
16 pct:=%
17 define nl
18
19
20 endef
21 # Shell-quote: a'b => 'a'\''b'
22 sq='$(subst ','\'',$1)'
23 # Make-quote: a$\nb => a$$$(nl)b
24 # This is enough to assign the value, *but not to use it as an argument!*
25 mqas=$(subst $(nl),$$(nl),$(subst $$,$$$$,$1))
26 # Return nonempty if the strings are equal, empty otherwise.
27 # If the strings have only nice characters, you can do $(filter x$1,x$2).
28 streq=$(findstring x$1,$(findstring x$2,x$1))
29
30 # $(call fmt-make-assignment,foo,bar)
31 # Output a make assignment that stores bar in variable foo.
32 # The result is like `foo:=bar' but handles leading spaces, $, and
33 # newlines appearing in bar safely.
34 fmt-make-assignment=$1:=$$(empty)$(call mqas,$2)
35
36
37 # TARGET OBFUSCATION
38
39 # Mage uses two kinds of "obfuscated targets" (those that are not the simple
40 # names of real files):
41 # - An always-exists target like /.//. is used as a prerequisite of an implicit
42 #   rule.  Since it exists, make doesn't second-expand its own prerequisites
43 #   until it is actually run.  This way, a target's prerequisites can depend on
44 #   the results of previous command scripts.
45 # - An alternative-name target like /.//./proc/self/cwd/bar is used to check the
46 #   mtime of a file without actually building it or introducing a circular
47 #   dependency.
48
49 # $(newoid) allocates and returns a new obfuscation ID (oid).  Example:
50 #    x:=$(newoid)
51 # You can then refer to target $x or $x$(aname)bar (for any file bar).
52 # Prerequisites and commands come from $($x@opr) and $($x@ocmd).
53 # Target-specific variables $(oid) and $(otgt) (if alternate-name) are
54 # available.
55
56 opfx:=/./
57 # TODO If the system doesn't support /proc/self/cwd, use something else.
58 aname:=/proc/self/cwd/
59 nextoid:=/./.
60 newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.))))
61
62 # Target-specific variables for obfuscated targets.
63 $(opfx)%: oid=$(word 1,$(subst $(aname), ,$@))
64 $(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@))
65
66 # High-priority implicit rule for obfuscated targets.  Works for both always-
67 # exists and alternate-name targets.
68 $(opfx)%: $$($$(oid)@opr)
69         $($(oid)@ocmd)
70
71 # Make won't use the same implicit rule more than once on its stack, so provide
72 # a second copy of the rule to allow two obfuscated targets on the stack.
73 # Needed for $(mg-genfile-oid)$(aname)bar.g <- $(mg-scout-oid)$(aname)bar .
74 # The prerequisites must look different before second-expansion so the second
75 # rule isn't discarded as a duplicate.
76 $(opfx)%: $$(empty) $$($$(oid)@opr)
77         $($(oid)@ocmd)
78
79 # MAIN BUILD LOGIC
80
81 # DESIGN:
82 #
83 # - To each generated file bar corresponds a "genfile" bar.g that contains some
84 # information about how bar was generated, including the command (for rebuild on
85 # command change) and the warnings (for replay).
86 #
87 # - bar.g's mtime is the last time Mage verified that bar was up to date.  bar
88 # needs to be regenerated iff a prerequisite is newer than *bar.g* (not bar).
89 # If a prerequisite changes but the command gives the same contents for bar,
90 # bar.g is touched but bar itself is not touched because files that depend on it
91 # need not be regenerated.
92 #
93 # - If bar is newer than bar.g, the user has overridden it, and we should leave
94 # the override in place but warn the user about it.
95 #
96 # - The user's Makefile defines Mage rules by calling mg-define-rule.  Each Mage
97 # rule becomes one underlying make rule with the same target and a little extra
98 # magic.  This is important so that all implicit rule competition takes place at
99 # the same target.  Additionally, the prerequisites are passed to an obfuscated
100 # target for bar.g to see if any are newer than bar.g.  This is done by the
101 # single obfuscated implicit rule, keeping the number of implicit rules low.
102 # Then the command script for bar checks for overrides, command change,
103 # prerequisite change, etc. and acts accordingly.
104 #
105 # Target metadata variables for bar: (* means stored in bar.g)
106 #
107 # bar@cmd:=cat foo >bar.tmp
108 #     Generation command as given to the shell.*
109 #
110 # bar@warnings:=$(empty)yikes!
111 #     Stuff the command printed to stdout or stderr.*
112 #
113 # bar@deps:=included@x oops@
114 #     If dependency-logging, list of filename@revision used.  Revision is x for
115 #     exists and empty for doesn't exist.  Later perhaps x will be the mtime.*
116 #
117 # bar@gloaded:=1
118 #     Set if Mage has loaded bar.g and hasn't changed it since then.
119 #
120 # bar@gdeps:=foo
121 #     Static dependencies of bar, for checking by bar.g.
122 #
123 # bar@gq:=foo $(mg-scout-oid)$(aname)bar
124 #     $? from the rule for bar.g; used by the rule for bar.
125 #
126 # bar@uptodate:=1
127 #     Set when Mage makes a file or depends on it being made.  Used to determine
128 #     the next prerequisite to check for a dependency-logging command.
129 #
130 ## If the rule for bar is overridden, we clear the information from bar.g so
131 ## that it is as if bar.g didn't exist. -- Not currently needed
132 #
133 # Some make features with which Mage's compatibility has not been investigated:
134 # - Command-line options (especially --dry-run, --question, --touch,
135 #   --always-make, --keep-going, --jobs, --assume-old, and --assume-new)
136 # - Static pattern rules
137 # - Vpath
138
139 # $(call gload,foo.o)
140 # Make sure foo.o's genfile, if any, has been loaded.
141 define gload
142 $(if $($1@gloaded),,$(eval 
143 $1@cmd:=
144 $1@warnings:=
145 $1@deps:=
146 -include $1.g
147 $1@gloaded:=1
148 ))
149 endef
150
151 # bar.g: scout bar and its dependencies and store $?.  When implicit rules
152 # compete for bar, we depend on the rule make uses being the last one it
153 # second-expands so that $(bar@gdeps) is still correct.
154 mg-genfile-oid:=$(newoid)
155 mg-scout-oid:=$(newoid)
156 $(mg-genfile-oid)@opr=$($(otgt:.g=)@gdeps) $(if $(wildcard $(otgt:.g=)),$(mg-scout-oid)$(aname)$(otgt:.g=),)
157 $(mg-genfile-oid)@ocmd=$(eval $(otgt:.g=)@gq:=$?)
158
159 # Mage-ized automatic variables.
160 # $@: needs no translation
161 # NOTE: $(mg@) is *eventual* target.  Commands must write to temp file, $t .
162 # $%: haven't thought about it much, but probably needs no translation
163 mg< = $(firstword $(mg^))
164 mg? = $(filter-out $(opfx)%,$($@@gq))
165 mg^ = $(filter-out MG-% $(opfx)%,$^)
166 mg+ = $(filter-out MG-% $(opfx)%,$+)
167 # $|: needs no translation
168 # $*: needs no translation
169
170 # $(call mg-translate-cmd,cat $$< >$$t)
171 # Replaces references to automatic variables with references to their Mage-ized
172 # counterparts.  There might be false matches, e.g., $$@ => $$(mg@) ;
173 # to prevent that, write $$$(empty)@ instead.  (c.f. autoconf empty quadrigraph)
174 define mg-translate-cmd
175 $(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
176 endef
177
178 # $(call prereq-predict,target,prerequisites)
179 # Expands to code that does the following at second-expansion time:
180 # 1. Computes the actual prerequisites from the given prerequisite patterns by
181 #    translating % to $* and prepending a directory if appropriate.
182 # 2. Saves both existing ($+) and newly computed prerequisites to $(bar@gdeps)
183 #    where the rule for bar.g can get them.
184 # Factor out set-gdeps because make gets confused if the macro for the
185 # prerequisites has a colon.  Make replaces the first % with the stem *before
186 # expansion*, so use $(pct) to protect some %s.
187 set-gdeps=$(eval $@@gdeps:=$1)
188 prereq-predict=$$$$(call set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2))))
189
190 # Used to make the rule for bar always run so we can act based on $(bar@gdeps)
191 # and check for command change.
192 MG-FORCE:
193 .PHONY: MG-FORCE
194
195 # $(call mg-define-rule,target,prerequisites,cmd)
196 # Defines a rule.  cmd is expanded again when it is run, at which time
197 # Mage-ized automatic variables are available.
198 define mg-define-rule
199 $(eval 
200
201 # Store the new command.
202 $1: cmd=$(call mg-translate-cmd,$3)
203 # Provide variable for the temporary file. FIX
204 $1: t=$$@.tmp
205
206 # Rule for the target.  Set $(bar@gdeps) to all prerequisites.  Apply the new
207 # prerequisites.  The command script always runs.  Depend on bar.g to get
208 # $(bar@gq).
209 $1: $(call prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g
210         $$(mg-rule-cmd)
211 )
212 endef
213
214 # TODO Provide a way to define static pattern rules.
215
216 # Procedure to generate bar.  Remember, $@ is bar.g.
217 # If an override:
218 #     1. Complain.  (Should we stop "`bar' is up to date" using @:; ?)
219 #     2. Clear out the warnings so we don't replay them.
220 # Otherwise, check prereqs, command, and nonexistence to decide whether bar
221 # needs to be regenerated.  If so:
222 #     1. Set a flag so Mage knows to reread the genfile when make runs target
223 #        "bar".
224 #     2. Echo the command being run.
225 #     On error, skip to 8:
226 #        3. Open the new genfile bar.g.tmp for writing.
227 #        4. Store the command in bar.g.tmp.
228 #        5. Run the command to bar.tmp, storing warnings in bar.g.tmp.
229 #        6. If bar.tmp differs from bar:
230 #           a. Clear and touch bar.g (so bar doesn't become an override if we're
231 #              killed between steps 6b and 7).
232 #           b. Move in the new bar.
233 #        7. Now that bar is known to be up to date, move in the new bar.g.
234 #     8. Defensively remove both temporary files and exit with the exit code
235 #        from before the removal. (trap EXIT)
236 # If not:
237 #     1. If there were warnings, replay them.  (HMMM To stderr?)
238 # HMMM .tmp in displayed command looks ugly
239 define mg-rule-cmd
240         $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\
241                 $(info Mage: warning: Manually created/modified file at $@ overrides rule.)\
242         ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,x)$(mg-check-cmd),\
243                 $(eval $@@gloaded:=)$(info $(cmd))\
244                 @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\
245                 exec 3>$@.g.tmp && $(mg-assign-cmd) >&3 &&\
246                 set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
247                 $(mg-maybe-move-target) && mv -f $@.g.tmp $@.g\
248         ,$(if $($@@warnings),\
249                 $(info $($@@cmd) # Mage warning replay$(nl)$($@@warnings))\
250         )))
251 endef
252
253 # If the command changed, we must regenerate.
254 mg-check-cmd=$(if $(call streq,$(cmd),$($@@cmd)),,x)
255
256 # Pieces of mg-generate that I factored out to make mg-generate more readable.
257 mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$(cmd)))
258 mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $@.tmp ] || { echo 'Mage: error: Command for $@ succeeded without creating it!'; false; }; }; }
259 mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/'
260 mg-maybe-move-target={ cmp -s $@ $@.tmp || echo >$@.g && mv -f $@.tmp $@; }
261
262 # Just add additional prerequisites.  This cannot add prerequisite patterns to
263 # an implicit rule, but it can add specific prerequisites to an individual use
264 # of an implicit rule.  Currently, Mage picks up the target's prerequisites from
265 # make, so this just attaches the given prerequisites to the target, but the
266 # implementation might change in the future.
267 define mg-define-prereq
268 $(eval $1: $2)
269 endef
270
271 # END
272
273 # We don't want anything we defined to become the default goal.
274 .DEFAULT_GOAL := $(mg-orig-default-goal)