| 1 | # Mgear Build Tool by Matt McCutchen |
| 2 | # http://www.kepreon.com/~matt/mgear/ |
| 3 | |
| 4 | |
| 5 | # Remember the original default goal so we can restore it at the end of mgear.mk. |
| 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 | |
| 13 | # Deletion of intermediate files messes everything up. |
| 14 | # TODO: See if we can relax this requirement. |
| 15 | .SECONDARY: |
| 16 | |
| 17 | |
| 18 | # TEXT UTILITIES |
| 19 | empty:= |
| 20 | bs:=\$(empty) |
| 21 | hash:=\# |
| 22 | comma:=, |
| 23 | pct:=% |
| 24 | define nl |
| 25 | |
| 26 | |
| 27 | endef |
| 28 | # Shell-quote: a'b => 'a'\''b' |
| 29 | sq='$(subst ','\'',$1)' |
| 30 | # Make-quote: a$\nb => a$$$(nl)b |
| 31 | # This is enough to assign the value, *but not to use it as an argument!* |
| 32 | mqas=$(subst $(hash),$$(hash),$(subst $(nl),$$(nl),$(subst $$,$$$$,$1))) |
| 33 | # Make the value safe to use as an argument without doubling $. |
| 34 | # *The implementation is incomplete*; I will improve it as needed. |
| 35 | msarg=$(subst $(comma),$$(comma),$1) |
| 36 | # Return nonempty if the strings are equal, empty otherwise. |
| 37 | # If the strings have only nice characters, you can do $(filter x$1,x$2). |
| 38 | streq=$(findstring x$1,$(findstring x$2,x$1)) |
| 39 | |
| 40 | # $(call fmt-make-assignment,foo,bar) |
| 41 | # Output a make assignment that stores bar in variable foo. |
| 42 | # The result is like `foo:=bar' but handles leading spaces, $, and |
| 43 | # newlines appearing in bar safely. |
| 44 | fmt-make-assignment=$1:=$$(empty)$(call mqas,$2) |
| 45 | |
| 46 | |
| 47 | # TARGET OBFUSCATION |
| 48 | |
| 49 | # Mgear uses two kinds of "obfuscated targets" (those that are not the simple |
| 50 | # names of real files): |
| 51 | # - An always-exists target like /.//. is used as a prerequisite of an implicit |
| 52 | # rule. Since it exists, make doesn't second-expand its own prerequisites |
| 53 | # until it is actually run. This way, a target's prerequisites can depend on |
| 54 | # the results of previous command scripts. |
| 55 | # - An alternative-name target like /.//./proc/self/cwd/bar is used to check the |
| 56 | # mtime of a file without actually building it or introducing a circular |
| 57 | # dependency. |
| 58 | |
| 59 | # $(newoid) allocates and returns a new obfuscation ID (oid). Example: |
| 60 | # x:=$(newoid) |
| 61 | # You can then refer to target $x or $x$(aname)bar (for any file bar). |
| 62 | # Prerequisites and commands come from $($x@opr) and $($x@ocmd). |
| 63 | # Target-specific variables $(oid) and $(otgt) (if alternate-name) are |
| 64 | # available. |
| 65 | |
| 66 | opfx:=/./ |
| 67 | # TODO If the system doesn't support /proc/self/cwd, use something else. |
| 68 | aname:=/proc/self/cwd/ |
| 69 | nextoid:=/./. |
| 70 | newoid=$(nextoid)$(eval nextoid:=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(nextoid:.=/.)))) |
| 71 | |
| 72 | # Target-specific variables for obfuscated targets. |
| 73 | $(opfx)%: oid=$(word 1,$(subst $(aname), ,$@)) |
| 74 | $(opfx)%: otgt=$(word 2,$(subst $(aname), ,$@)) |
| 75 | |
| 76 | # High-priority implicit rule for obfuscated targets. Works for both always- |
| 77 | # exists and alternate-name targets. |
| 78 | $(opfx)%: $$($$(oid)@opr) |
| 79 | $($(oid)@ocmd) |
| 80 | |
| 81 | # Make won't use the same implicit rule more than once on its stack, so provide |
| 82 | # a second copy of the rule to allow two obfuscated targets on the stack. |
| 83 | # Needed for $(mg-genfile-oid)$(aname)bar.g <- $(mg-scout-oid)$(aname)bar . |
| 84 | # The prerequisites must look different before second-expansion so the second |
| 85 | # rule isn't discarded as a duplicate. |
| 86 | $(opfx)%: $$(empty) $$($$(oid)@opr) |
| 87 | $($(oid)@ocmd) |
| 88 | |
| 89 | # MAIN BUILD LOGIC |
| 90 | |
| 91 | # DESIGN: |
| 92 | # |
| 93 | # - To each generated file bar corresponds a "genfile" bar.g that contains some |
| 94 | # information about how bar was generated, including the command (for rebuild on |
| 95 | # command change) and the warnings (for replay). |
| 96 | # |
| 97 | # - bar.g's mtime is the last time mgear verified that bar was up to date. bar |
| 98 | # needs to be regenerated iff a prerequisite is newer than *bar.g* (not bar). |
| 99 | # If a prerequisite changes but the command gives the same contents for bar, |
| 100 | # bar.g is touched but bar itself is not touched because files that depend on it |
| 101 | # need not be regenerated. |
| 102 | # |
| 103 | # - If bar is newer than bar.g, the user has overridden it, and we should leave |
| 104 | # the override in place but warn the user about it. |
| 105 | # |
| 106 | # - The user's Makefile defines mgear rules by calling mg-define-rule. Each |
| 107 | # mgear rule becomes one underlying make rule with the same target and a little |
| 108 | # extra magic. This is important so that all implicit rule competition takes |
| 109 | # place at the same target. Additionally, the prerequisites are passed to an |
| 110 | # obfuscated target for bar.g to see if any are newer than bar.g. This is done |
| 111 | # by the single obfuscated implicit rule, keeping the number of implicit rules |
| 112 | # low. Then the command script for bar checks for overrides, command change, |
| 113 | # prerequisite change, etc. and acts accordingly. |
| 114 | # |
| 115 | # Target metadata variables for bar: (* means stored in bar.g) |
| 116 | # Now some variables are reused instead of remembered for every target. |
| 117 | # |
| 118 | # @name:=bar |
| 119 | # Name of the target to which @cmd, @warnings, @deps refer. |
| 120 | # |
| 121 | # @cmd:=cat foo >bar.tmp |
| 122 | # Generation command as given to the shell.* |
| 123 | # |
| 124 | # @warnings:=$(empty)yikes! |
| 125 | # Data that the command printed to stdout or stderr, presumably warnings.* |
| 126 | # |
| 127 | # @deps:=included@x oops@ |
| 128 | # If dependency-logging, list of filename@revision used. Revision is x for |
| 129 | # exists and empty for doesn't exist. Later perhaps x will be the mtime.* |
| 130 | # |
| 131 | # bar@gdeps:=foo |
| 132 | # Static dependencies of bar, for checking by bar.g. |
| 133 | # |
| 134 | # bar@gq:=foo $(mg-scout-oid)$(aname)bar |
| 135 | # $? from the rule for bar.g; used by the rule for bar. |
| 136 | # |
| 137 | # bar@checked:=1 |
| 138 | # Set when mgear determines that a file is up to date or depends on it being |
| 139 | # so determined. Used to decide which prerequisite to check next for a |
| 140 | # dependency-logging command. |
| 141 | # |
| 142 | # bar@dlc-ran:=1 |
| 143 | # Indicates that mgear is in the middle of building bar using a dependency- |
| 144 | # logging command. Means that bar.g.tmp, not bar.g, is the most current |
| 145 | # genfile. |
| 146 | # |
| 147 | ## If the rule for bar is overridden, we clear the information from bar.g so |
| 148 | ## that it is as if bar.g didn't exist. -- Not currently needed |
| 149 | # |
| 150 | # Some make features with which mgear's compatibility has not been investigated: |
| 151 | # - Command-line options (especially --dry-run, --question, --touch, |
| 152 | # --always-make, --keep-going, --jobs, --assume-old, and --assume-new) |
| 153 | # - Static pattern rules |
| 154 | # - Vpath |
| 155 | |
| 156 | # $(call gload,foo.o) |
| 157 | # Load foo.o's genfile, if any, into $(@cmd), etc. |
| 158 | define gload |
| 159 | $(if $(filter $(@name),$1),,$(eval |
| 160 | # TODO: Store and check the version of mgear that wrote the genfile. |
| 161 | #@mgear-version:= |
| 162 | @cmd:= |
| 163 | @warnings:= |
| 164 | @deps:= |
| 165 | -include $1.g |
| 166 | @name:=$1 |
| 167 | )) |
| 168 | endef |
| 169 | @name:= |
| 170 | |
| 171 | # bar.g: scout bar and its dependencies and store $?. When implicit rules |
| 172 | # compete for bar, we depend on the rule make uses being the last one it |
| 173 | # second-expands so that $(bar@gdeps) is still correct. |
| 174 | mg-genfile-oid:=$(newoid) |
| 175 | mg-scout-oid:=$(newoid) |
| 176 | $(mg-genfile-oid)@opr=$($(otgt:.g=)@gdeps) $(if $(wildcard $(otgt:.g=)),$(mg-scout-oid)$(aname)$(otgt:.g=),) MG-FORCE |
| 177 | $(mg-genfile-oid)@ocmd=$(eval $(otgt:.g=)@gq:=$(filter-out MG-%,$?)) |
| 178 | $(mg-scout-oid)@opr:= |
| 179 | $(mg-scout-oid)@ocmd:= |
| 180 | |
| 181 | # Mgear-ized automatic variables. |
| 182 | # For now, if you really want the eventual target, write $$(@). |
| 183 | mg@ = $@.tmp |
| 184 | # $%: haven't thought about it much, but probably needs no translation |
| 185 | mg< = $(firstword $(mg^)) |
| 186 | mg? = $(filter-out $(opfx)%,$($@@gq)) |
| 187 | mg^ = $(filter-out MG-% $(opfx)%,$^) |
| 188 | mg+ = $(filter-out MG-% $(opfx)%,$+) |
| 189 | # $|: needs no translation |
| 190 | # $*: needs no translation |
| 191 | |
| 192 | # $(call mg-translate-cmd,cat $$< >$$@) |
| 193 | # Replaces references to automatic variables with references to their mgear-ized |
| 194 | # counterparts. There might be false matches, e.g., $$@ => $$(mg@) ; |
| 195 | # to prevent that, write $$$(empty)@ instead. (c.f. autoconf empty quadrigraph) |
| 196 | mg-translate-cmd=$(subst $$@,$$(mg@),$(subst $$?,$$(mg?),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))) |
| 197 | |
| 198 | # $(call mg-prereq-predict,target,prerequisites) |
| 199 | # Expands to code that does the following at second-expansion time: |
| 200 | # 1. Computes the actual prerequisites from the given prerequisite patterns by |
| 201 | # translating % to $* and prepending a directory if appropriate. |
| 202 | # 2. Saves both existing ($+) and newly computed prerequisites to $(bar@gdeps) |
| 203 | # where the rule for bar.g can get them. |
| 204 | # Factor out set-gdeps because make gets confused if the macro for the |
| 205 | # prerequisites has a colon. Make replaces the first % with the stem *before |
| 206 | # expansion*, so use $(pct) to protect some %s. |
| 207 | mg-set-gdeps=$(eval $@@gdeps:=$1) |
| 208 | mg-prereq-predict=$$$$(call mg-set-gdeps,$$$$+ $(if $(findstring /,$1),$$$$(subst $$$$(pct),$$$*,$2),$$$$(addprefix $$$$(dir $$$$@),$$$$(subst $$$$(pct),$$$$*,$2)))) |
| 209 | |
| 210 | # Used to make the rule for bar always run so we can act based on $(bar@gdeps) |
| 211 | # and check for command change. |
| 212 | MG-FORCE: |
| 213 | .PHONY: MG-FORCE |
| 214 | |
| 215 | # $(call mg-define-rule,target,prerequisites,cmdvar) |
| 216 | # Defines a rule. cmdvar is *the name of a variable* containing the command. |
| 217 | # The variable is read immediately with $(value) and then expanded in the scope |
| 218 | # of mgear-ized automatic variables each time the rule is run. |
| 219 | # |
| 220 | # I eradicated the target-specific variables because they fail when there are |
| 221 | # multiple implicit rules with the same target pattern. |
| 222 | # |
| 223 | # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new |
| 224 | # prerequisites. The command script always runs. Depend on bar.g to get |
| 225 | # $(bar@gq). |
| 226 | define mg-define-rule |
| 227 | $(eval $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-genfile-oid)$(aname)$$$$@.g |
| 228 | $$(call mg-rule-cmd,$(call msarg,$(call mg-translate-cmd,$(value $3))))) |
| 229 | endef |
| 230 | |
| 231 | # TODO Provide a way to define static pattern rules. |
| 232 | |
| 233 | # Procedure to generate bar. Now $@ is bar, so we say $@.g for bar.g. |
| 234 | # If an override: |
| 235 | # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) |
| 236 | # 2. This would be the place to clear bar's metadata if we wanted to. |
| 237 | # Otherwise, check prereqs, command, and nonexistence to decide whether bar |
| 238 | # needs to be regenerated. If so: |
| 239 | # 1. Note that the genfile is about to change. |
| 240 | # 2. Echo the command being run. |
| 241 | # On error, skip to 8: |
| 242 | # 3. Open the new genfile bar.g.tmp for writing. |
| 243 | # 4. Run the command to bar.tmp, storing warnings in bar.g.tmp. |
| 244 | # 5. Store the command in bar.g.tmp. Do it here to ensure that bar.g.tmp |
| 245 | # is at least as new as bar.tmp. |
| 246 | # 6. If bar.tmp differs from bar: |
| 247 | # a. Clear and touch bar.g (so bar doesn't become an override if we're |
| 248 | # killed between steps 6b and 7). |
| 249 | # b. Move in the new bar. |
| 250 | # 7. Now that bar is known to be up to date, move in the new bar.g. |
| 251 | # 8. Defensively remove both temporary files and exit with the exit code |
| 252 | # from before the removal. (trap EXIT) |
| 253 | # If not: |
| 254 | # 1. If there were warnings, replay them. (HMMM To stderr?) |
| 255 | # HMMM .tmp in displayed command looks ugly |
| 256 | define mg-rule-cmd |
| 257 | $(if $(filter $(mg-scout-oid)$(aname)$@,$($@@gq)),\ |
| 258 | $(info mgear: warning: Manually created/modified file at $@ overrides rule.)\ |
| 259 | ,$(call gload,$@)$(if $($@@gq)$(if $(wildcard $@),,TARGET-DNE)$(mg-check-cmd),\ |
| 260 | $(eval $@@gloaded:=)$(info $1)\ |
| 261 | @trap 'rm -f $@.tmp $@.g.tmp' EXIT &&\ |
| 262 | exec 3>$@.g.tmp &&\ |
| 263 | set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ |
| 264 | $(mg-assign-cmd) >&3 &&\ |
| 265 | $(mg-maybe-move-target) &&\ |
| 266 | mv -f $@.g.tmp $@.g\ |
| 267 | ,$(if $(@warnings),\ |
| 268 | $(info $1 # mgear warning replay$(nl)$(@warnings))\ |
| 269 | ))) |
| 270 | endef |
| 271 | |
| 272 | # If the command changed, we must regenerate. |
| 273 | # HMMM What if the working directory changes? Most likely, the command will |
| 274 | # also change and mgear will do the right thing. |
| 275 | mg-check-cmd=$(if $(call streq,$1,$(@cmd)),,COMMAND-CHANGED) |
| 276 | |
| 277 | # Pieces of mg-generate that I factored out to make mg-generate more readable. |
| 278 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,@cmd,$1)) |
| 279 | mg-run-cmd={ ($1) 2>&1 && { [ -r $@.tmp ] || { echo 'mgear: error: Command for $@ succeeded without creating it!'; false; }; }; } |
| 280 | mg-wrap-warnings=sed -re '1s/^/@warnings:=$$(empty)/; 1!s/^/@warnings+=$$(nl)/' |
| 281 | # Drat bash's lack of precedence between || and &&. Extra braces necessary. |
| 282 | mg-maybe-move-target={ cmp -s $@ $@.tmp || { echo >$@.g && mv -f $@.tmp $@; }; } |
| 283 | |
| 284 | # Just add additional prerequisites. This cannot add prerequisite patterns to |
| 285 | # an implicit rule, but it can add specific prerequisites to an individual use |
| 286 | # of an implicit rule. Currently, mgear picks up the target's prerequisites |
| 287 | # from make, so this just attaches the given prerequisites to the target, but |
| 288 | # the implementation might change in the future. |
| 289 | mg-define-prereq=$(eval $1: $2) |
| 290 | |
| 291 | |
| 292 | # CLEAN |
| 293 | |
| 294 | # A command that finds all genfiles in a given directory and deletes them and |
| 295 | # the corresponding generated files. If a file is overridden, only the genfile |
| 296 | # is deleted. |
| 297 | # |
| 298 | # Example: |
| 299 | # |
| 300 | # clean: |
| 301 | # $(call mg-clean-cmd,.) |
| 302 | # .PHONY: clean |
| 303 | |
| 304 | define mg-clean-cmd |
| 305 | @exec 3>&1 &&\ |
| 306 | find $1 -name '*.g' | while read gf; do\ |
| 307 | f="$${gf%.g}" &&\ |
| 308 | if ! [ "$$f" -nt "$$gf" ]; then\ |
| 309 | echo "rm -f '$$f'" >&3 && echo "$$f";\ |
| 310 | fi &&\ |
| 311 | echo "$$gf";\ |
| 312 | done | xargs rm -f |
| 313 | endef |
| 314 | |
| 315 | |
| 316 | # DEPENDENCY-LOGGING COMMANDS |
| 317 | |
| 318 | mg-dlc-static-run-oid:=$(mg-genfile-oid) |
| 319 | |
| 320 | # $(call mg-define-rule-dlc,target,static-prerequisites,cmd,dep-converter) |
| 321 | # Analogue of mg-define-rule for a dependency-logging command. |
| 322 | # I haven't decided on the format for the dep-converter yet. |
| 323 | define mg-define-rule-dlc |
| 324 | $(eval |
| 325 | |
| 326 | # FINISH |
| 327 | |
| 328 | # Rule for the target. Set $(bar@gdeps) to all prerequisites. Apply the new |
| 329 | # prerequisites. The command script always runs. Finally, do the static run |
| 330 | # and then the first dynamic check. |
| 331 | $1: $(call mg-prereq-predict,$1,$2) $2 MG-FORCE $(mg-dlc-static-run-oid)$(aname)$$$$@.g $$$$(call dlc-next-dchk,$$$$(target)) |
| 332 | # TODO: Move the finished file into place or something |
| 333 | # $$(call mg-rule-cmd,$3) |
| 334 | ) |
| 335 | endef |
| 336 | |
| 337 | #mg-dlc-next-dchk=$(call mg-dlc-next-dchk-1,$1,$(newoid)) |
| 338 | #mg-dlc-next-dchk-1=$(eval $2@opr=$$(call mg-dlc-dchk-pr,$1,$2))$2 |
| 339 | # |
| 340 | ## $(call mg-dlc-dchk-pr,target,oid) |
| 341 | #mg-dlc-dchk-pr=$(call mg-dlc-dchk-pr-1,$1,$2,$(call next-unchecked-prereq,$($1@deps))) |
| 342 | ## $(call mg-next-unchecked-prereq,foo.c bar.h baz.h) |
| 343 | #mg-next-unchecked-prereq=$(firstword $(foreach p,$1,$(if $($p@checked),,$p))) |
| 344 | #define mg-dlc-dchk-pr-1 |
| 345 | #$(if $3,$(call mg-dlc-drun,$1,$2,$3) $(call mg-dlc-next-dchk,$1),) |
| 346 | #endef |
| 347 | # |
| 348 | ## $(call mg-dlc-drun,target,oid,prereq). FINISH |
| 349 | #define mg-dlc-drun |
| 350 | #$(eval |
| 351 | #$2$(aname)$3@opr:=$3 |
| 352 | #$2$(aname)$3@ocmd:= |
| 353 | #)$2$(aname)$3 |
| 354 | #endef |
| 355 | # |
| 356 | # |
| 357 | ## END |
| 358 | |
| 359 | # We don't want anything we defined to become the default goal. |
| 360 | .DEFAULT_GOAL := $(mg-orig-default-goal) |