1 # Mgear Build Tool by Matt McCutchen
2 # http://www.kepreon.com/~matt/mgear/
5 # Remember the original default goal so we can restore it at the end of mgear.mk.
6 mg-orig-default-goal:=$(.DEFAULT_GOAL)
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.
23 # Shell-quote: a'b => 'a'\''b'
24 sq='$(subst ','\'',$1)'
25 # Make-quote: a$\nb => a$$$(nl)b
26 # This is enough to assign the value, *but not to use it as an argument!*
27 mqas=$(subst $(hash),$$(hash),$(subst $(nl),$$(nl),$(subst $$,$$$$,$1)))
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).
30 streq=$(findstring x$1,$(findstring x$2,x$1))
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.
36 fmt-make-assignment=$1:=$$(empty)$(call mqas,$2)
41 # Mgear uses two kinds of "obfuscated targets" (those that are not the simple
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
51 # $(newoid) allocates and returns a new obfuscation ID (oid). Example:
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
59 # TODO If the system doesn't support /proc/self/cwd, use something else.
60 aname:=/proc/self/cwd/
62 newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.))))
64 # Target-specific variables for obfuscated targets.
65 $(opfx)%: oid=$(word 1,$(subst $(aname), ,$@))
66 $(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@))
68 # High-priority implicit rule for obfuscated targets. Works for both always-
69 # exists and alternate-name targets.
70 $(opfx)%: $$($$(oid)@opr)
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)
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).
89 # - bar.g's mtime is the last time mgear verified that bar was up to date. bar
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.
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.
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,
105 # prerequisite change, etc. and acts accordingly.
107 # Target metadata variables for bar: (* means stored in bar.g)
109 # bar@cmd:=cat foo >bar.tmp
110 # Generation command as given to the shell.*
112 # bar@warnings:=$(empty)yikes!
113 # Data that the command printed to stdout or stderr, presumably warnings.*
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.*
120 # Set if mgear has loaded bar.g and hasn't changed it since then.
123 # Static dependencies of bar, for checking by bar.g.
125 # bar@gq:=foo $(mg-scout-oid)$(aname)bar
126 # $? from the rule for bar.g; used by the rule for bar.
129 # Set when mgear determines that a file is up to date or depends on it being
130 # so determined. Used to decide which prerequisite to check next for a
131 # dependency-logging command.
134 # Indicates that mgear is in the middle of building bar using a dependency-
135 # logging command. Means that bar.g.tmp, not bar.g, is the most current
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
141 # Some make features with which mgear's compatibility has not been investigated:
142 # - Command-line options (especially --dry-run, --question, --touch,
143 # --always-make, --keep-going, --jobs, --assume-old, and --assume-new)
144 # - Static pattern rules
147 # $(call gload,foo.o)
148 # Make sure foo.o's genfile, if any, has been loaded.
150 $(if $($1@gloaded),,$(eval
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:=$?)
167 # Mgear-ized automatic variables.
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
178 # $(call mg-translate-cmd,cat $$< >$$t)
179 # Replaces references to automatic variables with references to their mgear-ized
180 # counterparts. There might be false matches, e.g., $$@ => $$(mg@) ;
181 # to prevent that, write $$$(empty)@ instead. (c.f. autoconf empty quadrigraph)
182 mg-translate-cmd=$(subst $$t,$$@.tmp,$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))))
184 # $(call mg-prereq-predict,target,prerequisites)
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.
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))))
196 # Used to make the rule for bar always run so we can act based on $(bar@gdeps)
197 # and check for command change.
201 # $(call mg-define-rule,target,prerequisites,cmd)
202 # Defines a rule. cmd is expanded again when it is run, at which time
203 # Mgear-ized automatic variables are available.
205 # I eradicated the target-specific variables because they fail when there are
206 # multiple implicit rules with the same target pattern.
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
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)))
216 # TODO Provide a way to define static pattern rules.
218 # Procedure to generate bar. Now $@ is bar, so we say $@.g for bar.g.
220 # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?)
221 # 2. This would be the place to clear bar's metadata if we wanted to.
222 # Otherwise, check prereqs, command, and nonexistence to decide whether bar
223 # needs to be regenerated. If so:
224 # 1. Note that the genfile is about to change.
225 # 2. Echo the command being run.
226 # On error, skip to 8:
227 # 3. Open the new genfile bar.g.tmp for writing.
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.
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)
239 # 1. If there were warnings, replay them. (HMMM To stderr?)
240 # HMMM .tmp in displayed command looks ugly
242 $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\
243 $(info mgear: warning: Manually created/modified file at $@ overrides rule.)\
244 ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,TARGET-DNE)$(mg-check-cmd),\
245 $(eval $@@gloaded:=)$(info $1)\
246 @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\
248 set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
249 $(mg-assign-cmd) >&3 &&\
250 $(mg-maybe-move-target) &&\
252 ,$(if $($@@warnings),\
253 $(info $($@@cmd) # mgear warning replay$(nl)$($@@warnings))\
257 # If the command changed, we must regenerate.
258 mg-check-cmd=$(if $(call streq,$1,$($@@cmd)),,COMMAND-CHANGED)
260 # Pieces of mg-generate that I factored out to make mg-generate more readable.
261 mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$@@cmd,$1))
262 mg-run-cmd={ ($1) 2>&1 && { [ -r $@.tmp ] || { echo 'mgear: error: Command for $@ succeeded without creating it!'; false; }; }; }
263 mg-wrap-warnings=sed -re '1s/^/$@@warnings:=$$(empty)/; 1!s/^/$@@warnings+=$$(nl)/'
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 $@; }; }
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
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.
272 mg-define-prereq=$(eval $1: $2)
277 # A command that finds all genfiles in a given directory and deletes them and
278 # the corresponding generated files. If a file is overridden, only the genfile
284 # $(call mg-clean-cmd,.)
289 find $1 -name '*.g' | while read gf; do\
291 if ! [ "$$f" -nt "$$gf" ]; then\
292 echo "rm -f '$$f'" >&3 && echo "$$f";\
299 # DEPENDENCY-LOGGING COMMANDS
301 mg-dlc-static-run-oid:=$(mg-genfile-oid)
303 # $(call mg-define-rule-dlc,target,static-prerequisites,cmd,dep-converter)
304 # Analogue of mg-define-rule for a dependency-logging command.
305 # I haven't decided on the format for the dep-converter yet.
306 define mg-define-rule-dlc
311 # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new
312 # prerequisites. The command script always runs. Finally, do the static run
313 # and then the first dynamic check.
314 $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-dlc-static-run-oid)$(aname)$$$$@.g $$$$(call dlc-next-dchk,$$$$(target))
315 # TODO: Move the finished file into place or something
316 # $$(call mg-rule-cmd,$3)
320 #mg-dlc-next-dchk=$(call mg-dlc-next-dchk-1,$1,$(newoid))
321 #mg-dlc-next-dchk-1=$(eval $2@opr=$$(call mg-dlc-dchk-pr,$1,$2))$2
323 ## $(call mg-dlc-dchk-pr,target,oid)
324 #mg-dlc-dchk-pr=$(call mg-dlc-dchk-pr-1,$1,$2,$(call next-unchecked-prereq,$($1@deps)))
325 ## $(call mg-next-unchecked-prereq,foo.c bar.h baz.h)
326 #mg-next-unchecked-prereq=$(firstword $(foreach p,$1,$(if $($p@checked),,$p)))
327 #define mg-dlc-dchk-pr-1
328 #$(if $3,$(call mg-dlc-drun,$1,$2,$3) $(call mg-dlc-next-dchk,$1),)
331 ## $(call mg-dlc-drun,target,oid,prereq). FINISH
334 #$2$(aname)$3@opr:=$3
342 # We don't want anything we defined to become the default goal.
343 .DEFAULT_GOAL := $(mg-orig-default-goal)