From 00804b7cdba8a57acd8583d6326f728b5ce15516 Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Sun, 22 Apr 2007 15:26:24 -0400 Subject: [PATCH] Here's what I have so far related to the Mage build tool. --- demo/Makefile | 13 +++ demo/Makefile.plain | 14 +++ demo/clean | 2 + demo/foo | 5 ++ demo/mage.mk | 1 + experiments/bigint-mage-Makefile | 52 +++++++++++ experiments/bong | 2 + experiments/clean | 7 ++ experiments/cmd-assign-var.mk | 11 +++ experiments/collect.mk | 27 ++++++ experiments/dontcare.mk | 23 +++++ experiments/eval-commas.mk | 13 +++ experiments/flist.mk | 14 +++ experiments/genrecord | 31 +++++++ experiments/if-two-commands.mk | 11 +++ experiments/implicit-cutoff-cmd | 2 + experiments/implicit-cutoff.mk | 17 ++++ experiments/match-command.mk | 16 ++++ experiments/mk-find-shell-use | 4 + experiments/mkskeleton | 9 ++ experiments/obfuscation-dump.mk | 25 ++++++ experiments/precious.mk | 24 +++++ experiments/refuted-stamp.mk | 7 ++ experiments/remake.mk | 52 +++++++++++ experiments/required.mk | 1 + experiments/restat-no-cmd.mk | 13 +++ experiments/se-prereq-ts.mk | 16 ++++ experiments/sip | 20 +++++ experiments/test-notee | 4 + experiments/test-tee | 6 ++ experiments/transfer-prereqs.mk | 15 ++++ experiments/two-commands.mk | 7 ++ experiments/yucky-ts.mk | 5 ++ src/mage.mk | 145 +++++++++++++++++++++++++++++++ 34 files changed, 614 insertions(+) create mode 100644 demo/Makefile create mode 100644 demo/Makefile.plain create mode 100755 demo/clean create mode 100644 demo/foo create mode 120000 demo/mage.mk create mode 100644 experiments/bigint-mage-Makefile create mode 100644 experiments/bong create mode 100755 experiments/clean create mode 100644 experiments/cmd-assign-var.mk create mode 100644 experiments/collect.mk create mode 100644 experiments/dontcare.mk create mode 100644 experiments/eval-commas.mk create mode 100644 experiments/flist.mk create mode 100755 experiments/genrecord create mode 100644 experiments/if-two-commands.mk create mode 100755 experiments/implicit-cutoff-cmd create mode 100644 experiments/implicit-cutoff.mk create mode 100644 experiments/match-command.mk create mode 100755 experiments/mk-find-shell-use create mode 100755 experiments/mkskeleton create mode 100644 experiments/obfuscation-dump.mk create mode 100644 experiments/precious.mk create mode 100644 experiments/refuted-stamp.mk create mode 100644 experiments/remake.mk create mode 100644 experiments/required.mk create mode 100644 experiments/restat-no-cmd.mk create mode 100644 experiments/se-prereq-ts.mk create mode 100755 experiments/sip create mode 100755 experiments/test-notee create mode 100755 experiments/test-tee create mode 100644 experiments/transfer-prereqs.mk create mode 100644 experiments/two-commands.mk create mode 100644 experiments/yucky-ts.mk create mode 100644 src/mage.mk diff --git a/demo/Makefile b/demo/Makefile new file mode 100644 index 0000000..2e20b36 --- /dev/null +++ b/demo/Makefile @@ -0,0 +1,13 @@ +# Very simple Mage example + +include mage.mk + +.SECONDARY: + +foo.x.h: + +cmd-xgrep = grep 'X' '$<' >'$@' +$(call mg-rule,%.x,%,$(value cmd-xgrep)) + +cmd-addheader = echo 'Header:' | cat - '$<' >'$@' +$(call mg-rule,%.h,%,$(value cmd-addheader)) diff --git a/demo/Makefile.plain b/demo/Makefile.plain new file mode 100644 index 0000000..75cdb7a --- /dev/null +++ b/demo/Makefile.plain @@ -0,0 +1,14 @@ +# Very simple Mage example + +.SECONDARY: + +foo.x.h: + +cmd-xgrep = grep 'X' '$<' >'$@' +%.x: % + $(cmd-xgrep) + +cmd-addheader = echo 'Header:' | cat - '$<' >'$@' +%.h: % + $(cmd-addheader) + diff --git a/demo/clean b/demo/clean new file mode 100755 index 0000000..c7f9885 --- /dev/null +++ b/demo/clean @@ -0,0 +1,2 @@ +#!/bin/bash +rm -f foo.x foo.x.g foo.x.h foo.x.h.g diff --git a/demo/foo b/demo/foo new file mode 100644 index 0000000..cdde6a7 --- /dev/null +++ b/demo/foo @@ -0,0 +1,5 @@ +blah +Xblah +foo +Xfoo +Xstuffa diff --git a/demo/mage.mk b/demo/mage.mk new file mode 120000 index 0000000..73c6b44 --- /dev/null +++ b/demo/mage.mk @@ -0,0 +1 @@ +../src/mage.mk \ No newline at end of file diff --git a/experiments/bigint-mage-Makefile b/experiments/bigint-mage-Makefile new file mode 100644 index 0000000..d012c7e --- /dev/null +++ b/experiments/bigint-mage-Makefile @@ -0,0 +1,52 @@ +# +# Matt McCutchen's Big Integer Library +# + +include mage.mk + +# Mention default target. +all : + +# Implicit rule to compile C++ files. Modify to your taste. +$(call mg-rule,%.o,%.cc,g++ -c -O -Wall -Wextra -pedantic $$< -o $$@) + +# Components of the library. +library-objects = BigUnsigned.o BigInteger.o BigUnsignedInABase.o BigIntegerUtils.o +library-headers = NumberlikeArray.hh BigUnsigned.hh BigUnsignedInABase.hh BigInteger.hh BigIntegerLibrary.hh + +# To ``make the library'', make all its objects using the implicit rule. +library : $(library-objects) + +# Extra dependencies from `#include'. +$(call mg-prereq,BigUnsigned.o,NumberlikeArray.hh BigUnsigned.hh) +$(call mg-prereq,BigInteger.o,NumberlikeArray.hh BigUnsigned.hh BigInteger.hh) +$(call mg-prereq,BigUnsignedInABase.o,NumberlikeArray.hh BigUnsigned.hh BigUnsignedInABase.hh) +$(call mg-prereq,BigIntegerUtils.o,NumberlikeArray.hh BigUnsigned.hh BigUnsignedInABase.hh BigInteger.hh) + +# The rules below build a program that uses the library. They are preset to +# build ``sample'' from ``sample.cc''. You can change the name(s) of the +# source file(s) and program file to build your own program, or you can write +# your own Makefile. + +# Components of the program. +program = sample +program-objects = sample.o + +# Conservatively assume all the program source files depend on all the library +# headers. You can change this if it is not the case. +$(call mg-prereq,$(program-objects),$(library-headers)) + +# How to link the program. The implicit rule covers individual objects. +$(call mg-rule,$(program),$(program-objects) $(library-objects),g++ $$(program-objects) $$(library-objects) -o $$@) + +# Delete all generated files we know about. +clean : + rm -f $(library-objects) $(program-objects) $(program) + +# I removed the *.tag dependency tracking system because it had few advantages +# over manually entering all the dependencies. If there were a portable, +# reliable dependency tracking system, I'd use it, but I know of no such; +# cons and depcomp are almost good enough. + +# Come back and define default target. +all : library $(program) diff --git a/experiments/bong b/experiments/bong new file mode 100644 index 0000000..fba9db5 --- /dev/null +++ b/experiments/bong @@ -0,0 +1,2 @@ +ha: $$(info planning ha) hey + diff --git a/experiments/clean b/experiments/clean new file mode 100755 index 0000000..34f46c8 --- /dev/null +++ b/experiments/clean @@ -0,0 +1,7 @@ +#!/bin/bash +target="${1-.}" +find "$target" -name '*.g' | while read g; do + f="${g%.g}" + [ "$f" -nt "$g" ] || echo "$f" + echo "$g" +done | xargs rm -f diff --git a/experiments/cmd-assign-var.mk b/experiments/cmd-assign-var.mk new file mode 100644 index 0000000..acfbe5c --- /dev/null +++ b/experiments/cmd-assign-var.mk @@ -0,0 +1,11 @@ +# It's fine to assign variables after make has snapped deps. + +all: foo bar + +foo: + $(eval MYVAR=value) + touch foo + +bar: + echo "The variable says $(MYVAR)" + touch bar diff --git a/experiments/collect.mk b/experiments/collect.mk new file mode 100644 index 0000000..233fa9c --- /dev/null +++ b/experiments/collect.mk @@ -0,0 +1,27 @@ +main: prepare collect + touch $@ + +prepare: + echo "Making $@" + echo "Try to turn off the switch" + touch collect + +collect: | t1 t2 t3 + echo "Making $@" + +t%: + echo "Making $@" + #false + touch $@ + +#dummy: + +#collect: | dummy +#collect: t1 +#collect: | t2 +#collect: | t3 +#collect: t4 +#collect: | t5 +#collect: t6 +#collect: t7 +#collect: | t8 diff --git a/experiments/dontcare.mk b/experiments/dontcare.mk new file mode 100644 index 0000000..992b2ed --- /dev/null +++ b/experiments/dontcare.mk @@ -0,0 +1,23 @@ +# Investigating dontcare (-include) targets to see if they're good for anything. + +# Observation 1: If a dontcare target fails during makefile remaking and is +# needed during the real run, make gives a misleading error that it has no +# rule instead of reporting the original failure. + +# Observation 2: If a required target fails, make makes do with the original +# error message. + +include required.mk +-include optional.mk + +all: required.mk + +fail%: + @echo "Failing to make $@" + false + +required.mk: fail1 + echo "Trying to make $@" + +optional.mk: fail1 + echo "Trying to make $@" diff --git a/experiments/eval-commas.mk b/experiments/eval-commas.mk new file mode 100644 index 0000000..138427f --- /dev/null +++ b/experiments/eval-commas.mk @@ -0,0 +1,13 @@ +all: + +include ../makeman/makeman.mk + +define macro +$(info $(filter $1,$(var)))$(eval $$(info $$(filter $(call mq,$1),$$(var)))) +endef + +var := xxx,yyy( +$(info $(var)) +$(info $(subst $(rp),$$(rp),$(var))) +$(info $(call mq,$(var))) +$(call macro,$(var)) diff --git a/experiments/flist.mk b/experiments/flist.mk new file mode 100644 index 0000000..734c776 --- /dev/null +++ b/experiments/flist.mk @@ -0,0 +1,14 @@ +# Seeing how long it takes Make to recursively list a directory. + +define flist1 +$(if $3,$(foreach 4,$3,$(call flist,$1,$4)),$(info $(2:$1/%=%))) +endef + +define flist +$(call flist1,$1,$2,$(filter-out %/. %/..,$(wildcard $2/.* $2/*))) +endef + +flist-%: + $(call flist,$*,$*) + +flist-.: diff --git a/experiments/genrecord b/experiments/genrecord new file mode 100755 index 0000000..9d86947 --- /dev/null +++ b/experiments/genrecord @@ -0,0 +1,31 @@ +#!/usr/bin/perl +# FIX: Should use perl from path +# genrecord: make a Mage genfile +# { gcc prog.c -o prog.tmp; echo $?; } 2>&1 | genrecord prog 'gcc prog.c -o prog.tmp' + +sub mqas($) { + my $s = $_[0]; + $s =~ s/\$/\$\$/g; + $s =~ s/\n/\$(nl)/g; + return $s; +} +sub fmtMakeAssignment($$) { + my ($n, $v) = @_; + $v = mqas($v); + return "$n:=\$(empty)$v"; +} + +my ($target, $cmd) = @ARGV; + +my @errorLines = ; +my $exitCode = 0; +if ($errorLines[$#errorLines] =~ /^(\d+)$/) { + chomp($exitCode = pop @errorLines); +} +my $errorText = join '', @errorLines; + +my $genfileText = fmtMakeAssignment("cmd-$target", $cmd) . "\n"; + . fmtMakeAssignment("errors-$target", $errorText) . "\n" + . fmtMakeAssignment("exitcode-$target", $exitCode) . "\n"; + +open GENFILE, '>', "$target.g.tmp" and print GENFILE $genfileText and close GENFILE or die; diff --git a/experiments/if-two-commands.mk b/experiments/if-two-commands.mk new file mode 100644 index 0000000..c0cf059 --- /dev/null +++ b/experiments/if-two-commands.mk @@ -0,0 +1,11 @@ +# Make expands the command script as a whole before splitting it into lines +# and executing each line. + +define mm-gen-commands +exec 3>$@.tmp && echo $(call sq,$(call fmt-make-assignment,cmd-$(target),$(cmd))) >&3 && set -o pipefail && { ($(cmd)) 2>&1 | sed -re '1s/^/errors-$$(target):=$$(empty)/; 1!s/^/errors-$$(target)+=$$(nl)/' >&3; xc=$$?; } && { [ $xc == 0 ] || echo "exitcode-$(target):=$$xc" >&3 } && if [ $xc != 0 ] || cmp -s $(target) $(target).tmp; then mv -f $@.tmp $@; else rm -f $(target) && mv -f $@.tmp $@ && mv -f $(target).tmp $(target); fi +echo bar +endef + +phooey: + $(if x,$(mm-gen-commands),) + $(eval include $@) diff --git a/experiments/implicit-cutoff-cmd b/experiments/implicit-cutoff-cmd new file mode 100755 index 0000000..dc4eda8 --- /dev/null +++ b/experiments/implicit-cutoff-cmd @@ -0,0 +1,2 @@ +#!/bin/bash +make -R -f implicit-cutoff.mk ic.xa diff --git a/experiments/implicit-cutoff.mk b/experiments/implicit-cutoff.mk new file mode 100644 index 0000000..7debf63 --- /dev/null +++ b/experiments/implicit-cutoff.mk @@ -0,0 +1,17 @@ +.SECONDEXPANSION: + +n= +s=$n $n + +FORCE: $$(info $$splanning $$@) + $(info $srunning $@) + +%.xa: $$(info $$splanning $$@) %.xb %.xc $$(if $$(wildcard $$*.xc),,$$(info $$*.xc needs touching -> $$(shell [ -r $$*.xc ] && echo 'already exists' || (sleep 1 && touch $$*.xc)))) + $(info $srunning $@) + +%.xb: $$(info $$splanning $$@) FORCE + $(info $srunning $@) + +%.xc: $$(info $$splanning $$@) FORCE + $(info $srunning $@) + diff --git a/experiments/match-command.mk b/experiments/match-command.mk new file mode 100644 index 0000000..04aaeec --- /dev/null +++ b/experiments/match-command.mk @@ -0,0 +1,16 @@ +# Can I parse makefile lines using Make's macro processor? + +define test-cmd +$(info $(if $(findstring ^ ,^$1),yes,no): $1) +endef + +empty := +cmd-1 := $(empty) echo bar +not-cmd-1 := $(empty) bar: foo +not-cmd-2 := $(empty) baz: bar + +$(call test-cmd,$(cmd-1)) +$(call test-cmd,$(not-cmd-1)) +$(call test-cmd,$(not-cmd-2)) + +all: diff --git a/experiments/mk-find-shell-use b/experiments/mk-find-shell-use new file mode 100755 index 0000000..0a27607 --- /dev/null +++ b/experiments/mk-find-shell-use @@ -0,0 +1,4 @@ +#!/bin/bash +sed -n -r -f /dev/fd/3 3<<'EOS' "$@" +s/^ (([^$'(){}<>|;&*?`]|'[^']*'|\$[^(]|\$\([^)]*\))*)([(){}<>|;&*?`]|\$\$)(.*)$/\1SHELL!\3!\4/p +EOS diff --git a/experiments/mkskeleton b/experiments/mkskeleton new file mode 100755 index 0000000..d3c698c --- /dev/null +++ b/experiments/mkskeleton @@ -0,0 +1,9 @@ +#!/bin/bash +set -x +dest="$1" +shift +rm -r "$dest" +src_args="$@" +parents=("${src_args[@]%/*}") +mkdir -p "${parents[@]/#/$dest}" +find "$dest" -type d -exec touch -d @0 + diff --git a/experiments/obfuscation-dump.mk b/experiments/obfuscation-dump.mk new file mode 100644 index 0000000..aaa0953 --- /dev/null +++ b/experiments/obfuscation-dump.mk @@ -0,0 +1,25 @@ +.SECONDEXPANSION: + +obfn1=/./. +#obfn=$(obfn1)$(eval obfn1=$(patsubst /.//////%,/.//./%,$(subst /.//////,//./,$(obfn1)/))) +define obfn +$(obfn1)$(eval +pr$(obfn1)=$1 +cmd$(obfn1)=$2 +obfn1=$(subst /.//////,//./,$(patsubst /.//////%,/././%,$(obfn1:.=/.))) +) +endef + +digits=0 1 2 3 4 5 6 7 8 9 + +%yxxx: $(foreach d,$(digits),%$dyxx) + +%yxx: $(foreach d,$(digits),%$dyx) + +%yx: $(foreach d,$(digits),%$dy) + +%y: $$(call obfn,FORCE,$$$$(info Running $$@ $$$$@)) + +/./%: $$(pr$$@) + $(cmd$@) +FORCE: diff --git a/experiments/precious.mk b/experiments/precious.mk new file mode 100644 index 0000000..54be133 --- /dev/null +++ b/experiments/precious.mk @@ -0,0 +1,24 @@ +all: bar baz + +.DELETE_ON_ERROR: + +.PRECIOUS: %.g + +.SECONDEXPANSION: + +%: %.g + # updating $@ + # the error occurs HERE! do we delete $@? + $(info echo bing\ + echo bong) + false + +%.g: %.in + # updating $@ + touch $*.g + touch $* + +%: %.in2 + # updating baz + touch $@ + false diff --git a/experiments/refuted-stamp.mk b/experiments/refuted-stamp.mk new file mode 100644 index 0000000..6ceaff3 --- /dev/null +++ b/experiments/refuted-stamp.mk @@ -0,0 +1,7 @@ +bar.g: $(wildcard $(abspath bar)) foo + $(if $(filter $(abspath bar),$?),$(error ./bar obstructs build),) + # Making bar.g + touch bar bar.g + +bar: bar.g + # Making bar diff --git a/experiments/remake.mk b/experiments/remake.mk new file mode 100644 index 0000000..4c61537 --- /dev/null +++ b/experiments/remake.mk @@ -0,0 +1,52 @@ +# We've established that +# bing: $$(eval include bong) +# causes a segfault. We don't know why but it isn't important. + +# Interesting: make plans any target that is mentioned, even if it isn't +# needed for any goal or makefile!!! + +# Goal: What makefiles get remade? + +.SECONDEXPANSION: + +#-include bing + +#all: $$(info planning $$@) one two three +# $(info running $@) + +#irrelevant quax zong: %: $$(info planning $$@) other +# $(info running irrelevant) + +%.xa: $$(info planning $$@ 1) %.xb %.xc signal-%-1 + $(info running $@ 1) + +%.xa: $$(info planning $$@ 2) %.xc %.xd signal-%-2 + $(info running $@ 2) + +signal-%: $$(info good so far $$*) + $(info running $@) + +%.xb: $$(info planning $$@) %.xe %.xf + $(info running $@) + +%.xc: $$(info planning $$@) %.xg %.xh + $(info running $@) + +%.xd: $$(info planning $$@) %.xi %.xj + $(info running $@) + +landmark: $$(info planning $$@) tat.xa + $(info running $@) + +#one two three: %: $$(info planning $$@) +# $(info running $@) + +#do-dontcare-include=$(eval -include $1) + +#bing: $$(info planning $$@) $$(call do-dontcare-include,bong) other +# echo $@ +# $(info running $@) +#other: $$(info planning $$@) +# $(info running $@) + +#ha: $$(info 1 planning $$@) diff --git a/experiments/required.mk b/experiments/required.mk new file mode 100644 index 0000000..53e344c --- /dev/null +++ b/experiments/required.mk @@ -0,0 +1 @@ +# This is required! diff --git a/experiments/restat-no-cmd.mk b/experiments/restat-no-cmd.mk new file mode 100644 index 0000000..bbdec9a --- /dev/null +++ b/experiments/restat-no-cmd.mk @@ -0,0 +1,13 @@ +baz: bar + touch $@ + +bar: bar.g.replay ; + +.PHONY: bar.g.replay +bar.g.replay: bar.g + # $@ + +FORCE: +bar.g: FORCE + touch $@ + touch bar diff --git a/experiments/se-prereq-ts.mk b/experiments/se-prereq-ts.mk new file mode 100644 index 0000000..92414c4 --- /dev/null +++ b/experiments/se-prereq-ts.mk @@ -0,0 +1,16 @@ +# References to target-specific variables are recognized in a prerequisite list +# undergoing second expansion. + +.SECONDEXPANSION: + +bar.g: target = $(@:.g=) + +define nl + + +endef + +thetext := hi umm$(nl)there + +bar.g: $$(info $$(target)) foo + # $(target) diff --git a/experiments/sip b/experiments/sip new file mode 100755 index 0000000..12e0579 --- /dev/null +++ b/experiments/sip @@ -0,0 +1,20 @@ +#!/bin/bash +# Simple include processor as an example of a dependency-logging command. +# sip input output depoutput +# Follows `include foo' at the beginning of a line. + +exec 3>"$3" +exec >"$2" + +function do_read { + echo "$1" >&3 + while IFS='' read line; do + if [[ "$line" == "include "* ]]; then + do_read "${line#include }" + else + echo "$line" + fi + done <"$1" +} + +do_read "$1" diff --git a/experiments/test-notee b/experiments/test-notee new file mode 100755 index 0000000..0efb5cd --- /dev/null +++ b/experiments/test-notee @@ -0,0 +1,4 @@ +#!/usr/bin/perl +while () { + print STDOUT $_; +} diff --git a/experiments/test-tee b/experiments/test-tee new file mode 100755 index 0000000..62bca0c --- /dev/null +++ b/experiments/test-tee @@ -0,0 +1,6 @@ +#!/usr/bin/perl +open OTHER, ">/dev/stdout"; +while () { + print STDOUT $_; + print OTHER $_; +} diff --git a/experiments/transfer-prereqs.mk b/experiments/transfer-prereqs.mk new file mode 100644 index 0000000..cc901f2 --- /dev/null +++ b/experiments/transfer-prereqs.mk @@ -0,0 +1,15 @@ +# Experimenting with the accumulation of prerequisites among explicit and +# implicit rules. + +.SECONDEXPANSION: + +%.x: ding3 $$(info implicit2 $$@ $$^) ; + +%.x: ding4 $$(info implicit1 $$@ $$^) + # 2 $@ + +foo.x: ding1 $$(info explicit1 $$@ $$^) +foo.x: ding2 $$(info explicit2 $$@ $$^) + +ding%: + # $@ diff --git a/experiments/two-commands.mk b/experiments/two-commands.mk new file mode 100644 index 0000000..5bf9b30 --- /dev/null +++ b/experiments/two-commands.mk @@ -0,0 +1,7 @@ +# How does Make execute two very simple commands? Since they don't have any +# punctuation that looks like it needs a shell, Make word-splits and executes +# them itself. Strace me! + +all: + echo foo + echo bar diff --git a/experiments/yucky-ts.mk b/experiments/yucky-ts.mk new file mode 100644 index 0000000..089e480 --- /dev/null +++ b/experiments/yucky-ts.mk @@ -0,0 +1,5 @@ +# Yes, it's legal to assign a yucky value to a target-specific variable. + +all: ts = ),}($$,{ +all: + # $(ts) diff --git a/src/mage.mk b/src/mage.mk new file mode 100644 index 0000000..465a05a --- /dev/null +++ b/src/mage.mk @@ -0,0 +1,145 @@ +# Mage by Matt McCutchen + +# Text utilities +empty := +bs := \$(empty) +define nl + + +endef +# Shell-quote: a'b => 'a'\''b' +sq = '$(subst ','\'',$1)' +# Make-quote: a$\nb => a$$$(nl)b +# This is enough to assign the value, *but not to use it as an argument!* +mqas = $(subst $(nl),$$(nl),$(subst $$,$$$$,$1)) +# Return nonempty if the strings are equal, empty otherwise. +# If the strings have only nice characters, you can do $(filter x$1,x$2). +streq = $(findstring x$1,$(findstring x$2,x$1)) + +# $(call fmt-make-assignment,foo,bar) +# Output a make assignment that stores bar in variable foo. +# The result is like `foo:=bar' but handles leading spaces, $, and +# newlines appearing in bar safely. +fmt-make-assignment = $1:=$$(empty)$(call mqas,$2) + +mg-orig-default-goal := $(.DEFAULT_GOAL) + +# bar.g file format: +# cmd-bar:=cat foo >bar.tmp +# errors-bar:=$(empty)yikes! +# exitcode-bar:=0 + +# $(call mg-check-file,foo.o) +# Make sure foo.o's genfile, if any, has been loaded. +define mg-check-file +$(if $(mg-checked-$1),,$(eval +cmd-$1 := +errors-$1 := +exitcode-$1 := +-include $1.g +mg-checked-$1 := done +)) +endef + +# $(call mg-translate-cmd,touch $$@) +# Currently translates $@, $<, $^, $+ to Mage-ized variables. +# $* needs no translation. We won't support $%, $?, $|. +# There might be false matches, e.g., $$@ => $$(out) ; +# to prevent that, do $$$(empty)@ . +define mg-translate-cmd +$(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) +endef + +# OOOH!!! .SECONDEXPANSION does let us watch the implicit rule search as it +# happens. +.SECONDEXPANSION: + +# $(call mg-rule,target,prerequisite,cmd) +# Defines a rule. +# If cmd uses $@, quote if necessary so this function sees $@, etc. +# +# When we build bar from foo using a Mage rule: +# Division of work: +# - "bar.g: foo" does the computation. +# - "bar: bar.g" replays errors and exit code. +# Cases: +# - bar is managed and up to date => replay +# - bar doesn't exist or is managed and out of date => compute +# - bar is unmanaged => warn about override +MG-FORCE-TARGET-DNE: +MG-FORCE-CMD-CHANGED: +MG-FORCE-REPLAY: +.PHONY: MG-FORCE-TARGET-DNE MG-FORCE-CMD-CHANGED MG-FORCE-REPLAY +define mg-rule +$(eval + +# Define some target-specific variables. +# It might look like we could use $*, but $1 most likely isn't %. +$1.g: target = $$(@:.g=) +$1.g: cmd = $(call mg-translate-cmd,$3) +$1.g: mg@ = $$(target).tmp +$1.g: mg^ = $$(filter-out MG-% $$(abspath $$(target)),$$^) +$1.g: mg+ = $$(filter-out MG-% $$(abspath $$(target)),$$+) +$1.g: mg< = $$(firstword $$(mg^)) + +# Load the target's genfile if necessary. +$1.g: $$$$(call mg-check-file,$$$$(target)) + +# If the target doesn't exist, we must run the rule; we'll generate it. +# If the target is unmanaged, we must run the rule; we'll see that the +# obfuscated target is in $? and complain. +$1.g: $$$$(if $$$$(wildcard $$$$(target)),$$$$(abspath $$$$(target)),$$$$(if $$(exitcode-$$$$(target)),,MG-FORCE-TARGET-DNE)) + +# If the command changed, we must regenerate. +$1.g: $$$$(if $$$$(call streq,$$$$(cmd),$$$$(cmd-$$$$(target))),,MG-FORCE-CMD-CHANGED) + +$1.g: $2 + $(value mg-commands) + +# Replay the errors and the exit code (if any of either). +# We don't have to worry about .DELETE_ON_ERROR deleting the target because +# it is only touched if we *successfully* regenerate it. +# Recheck the file in case it was regenerated. +# HMMM Maybe errors should go to stderr??? +$1: $1.g MG-FORCE-REPLAY + $$(call mg-check-file,$$@) + $$(if $$(errors-$$@)$$(exitcode-$$@)$$(built-$$@),$$(info $$(cmd-$$@)$$(if $$(built-$$@),, [replay]))$$(if $$(errors-$$@),$$(info $$(errors-$$@)),),) + $$(if $$(exitcode-$$@),@exit $$(exitcode-$$@),) +) +endef + +# Just add additional prerequisites. +define mg-prereq +$(eval +$1.g: $2 +) +endef + +# If an override: +# 1. Complain. +# 2. Clear out the errors and exit code so we don't replay them. +# Otherwise: +# 1. Store the command. +# 2. Run the command, storing errors. +# 3. Store exit code. +# 4. Update the real files as applicable. +# 5. Read the new genfile. +define mg-commands + $(if $(filter $(abspath $(target)),$?),\ + $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\ + $(eval cmd-$(target):=)$(eval errors-$(target):=)$(eval exitcode-$(target):=)\ + ,\ + @$(eval mg-checked-$(target) :=)$(eval built-$(target) := yes)\ + exec 3>$@.tmp &&\ + echo $(call sq,$(call fmt-make-assignment,cmd-$(target),$(cmd))) >&3 &&\ + set -o pipefail &&\ + { { ($(cmd)) && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } 2>&1\ + | sed -re '1s/^/errors-$(target):=$$(empty)/; 1!s/^/errors-$(target)+=$$(nl)/' >&3; xc=$$?; } &&\ + { [ $$xc == 0 ] || echo "exitcode-$(target):=$$xc" >&3; } &&\ + if [ $$xc != 0 ] || cmp -s $(target) $(target).tmp; then mv -f $@.tmp $@ && rm -f $(target).tmp;\ + else rm -f $(target) && mv -f $@.tmp $@ && mv -f $(target).tmp $(target); fi\ + ) +endef + +# We don't want anything we defined to become the default goal. +.DEFAULT_GOAL := $(mg-orig-default-goal) \ No newline at end of file -- 2.34.1