autoconf: Automatic Rule Rewriting
1
1 12.18.4 Automatic Rule Rewriting
1 --------------------------------
1
1 Some `make' implementations, such as Solaris and Tru64, search for
1 prerequisites in `VPATH' and then rewrite each occurrence as a plain
1 word in the rule. For instance:
1
1 # This isn't portable to GNU make.
1 VPATH = ../pkg/src
1 f.c: if.c
1 cp if.c f.c
1
1 executes `cp ../pkg/src/if.c f.c' if `if.c' is found in `../pkg/src'.
1
1 However, this rule leads to real problems in practice. For example,
1 if the source directory contains an ordinary file named `test' that is
1 used in a dependency, Solaris `make' rewrites commands like `if test -r
1 foo; ...' to `if ../pkg/src/test -r foo; ...', which is typically
1 undesirable. In fact, `make' is completely unaware of shell syntax
1 used in the rules, so the VPATH rewrite can potentially apply to _any_
1 whitespace-separated word in a rule, including shell variables,
1 functions, and keywords.
1
1 $ mkdir build
1 $ cd build
1 $ cat > Makefile <<'END'
1 VPATH = ..
1 all: arg func for echo
1 func () { for arg in "$$@"; do echo $$arg; done; }; \
1 func "hello world"
1 END
1 $ touch ../arg ../func ../for ../echo
1 $ make
1 ../func () { ../for ../arg in "$@"; do ../echo $arg; done; }; \
1 ../func "hello world"
1 sh: syntax error at line 1: `do' unexpected
1 *** Error code 2
1
1 To avoid this problem, portable makefiles should never mention a source
1 file or dependency whose name is that of a shell keyword like `for' or
1 `until', a shell command like `cat' or `gcc' or `test', or a shell
1 function or variable used in the corresponding `Makefile' recipe.
1
1 Because of these problems GNU `make' and many other `make'
1 implementations do not rewrite commands, so portable makefiles should
1 search `VPATH' manually. It is tempting to write this:
1
1 # This isn't portable to Solaris make.
1 VPATH = ../pkg/src
1 f.c: if.c
1 cp `test -f if.c || echo $(VPATH)/`if.c f.c
1
1 However, the "prerequisite rewriting" still applies here. So if `if.c'
1 is in `../pkg/src', Solaris and Tru64 `make' execute
1
1 cp `test -f ../pkg/src/if.c || echo ../pkg/src/`if.c f.c
1
1 which reduces to
1
1 cp if.c f.c
1
1 and thus fails. Oops.
1
1 A simple workaround, and good practice anyway, is to use `$?' and
1 `$@' when possible:
1
1 VPATH = ../pkg/src
1 f.c: if.c
1 cp $? $@
1
1 but this does not generalize well to commands with multiple
1 prerequisites. A more general workaround is to rewrite the rule so that
1 the prerequisite `if.c' never appears as a plain word. For example,
1 these three rules would be safe, assuming `if.c' is in `../pkg/src' and
1 the other files are in the working directory:
1
1 VPATH = ../pkg/src
1 f.c: if.c f1.c
1 cat `test -f ./if.c || echo $(VPATH)/`if.c f1.c >$@
1 g.c: if.c g1.c
1 cat `test -f 'if.c' || echo $(VPATH)/`if.c g1.c >$@
1 h.c: if.c h1.c
1 cat `test -f "if.c" || echo $(VPATH)/`if.c h1.c >$@
1
1 Things get worse when your prerequisites are in a macro.
1
1 VPATH = ../pkg/src
1 HEADERS = f.h g.h h.h
1 install-HEADERS: $(HEADERS)
1 for i in $(HEADERS); do \
1 $(INSTALL) -m 644 \
1 `test -f $$i || echo $(VPATH)/`$$i \
1 $(DESTDIR)$(includedir)/$$i; \
1 done
1
1 The above `install-HEADERS' rule is not Solaris-proof because `for i
1 in $(HEADERS);' is expanded to `for i in f.h g.h h.h;' where `f.h' and
1 `g.h' are plain words and are hence subject to `VPATH' adjustments.
1
1 If the three files are in `../pkg/src', the rule is run as:
1
1 for i in ../pkg/src/f.h ../pkg/src/g.h h.h; do \
1 install -m 644 \
1 `test -f $i || echo ../pkg/src/`$i \
1 /usr/local/include/$i; \
1 done
1
1 where the two first `install' calls fail. For instance, consider
1 the `f.h' installation:
1
1 install -m 644 \
1 `test -f ../pkg/src/f.h || \
1 echo ../pkg/src/ \
1 `../pkg/src/f.h \
1 /usr/local/include/../pkg/src/f.h;
1
1 It reduces to:
1
1 install -m 644 \
1 ../pkg/src/f.h \
1 /usr/local/include/../pkg/src/f.h;
1
1 Note that the manual `VPATH' search did not cause any problems here;
1 however this command installs `f.h' in an incorrect directory.
1
1 Trying to quote `$(HEADERS)' in some way, as we did for `foo.c' a
1 few makefiles ago, does not help:
1
1 install-HEADERS: $(HEADERS)
1 headers='$(HEADERS)'; \
1 for i in $$headers; do \
1 $(INSTALL) -m 644 \
1 `test -f $$i || echo $(VPATH)/`$$i \
1 $(DESTDIR)$(includedir)/$$i; \
1 done
1
1 Now, `headers='$(HEADERS)'' macro-expands to:
1
1 headers='f.h g.h h.h'
1
1 but `g.h' is still a plain word. (As an aside, the idiom
1 `headers='$(HEADERS)'; for i in $$headers;' is a good idea if
1 `$(HEADERS)' can be empty, because some shells diagnose a syntax error
1 on `for i in;'.)
1
1 One workaround is to strip this unwanted `../pkg/src/' prefix
1 manually:
1
1 VPATH = ../pkg/src
1 HEADERS = f.h g.h h.h
1 install-HEADERS: $(HEADERS)
1 headers='$(HEADERS)'; \
1 for i in $$headers; do \
1 i=`expr "$$i" : '$(VPATH)/\(.*\)'`;
1 $(INSTALL) -m 644 \
1 `test -f $$i || echo $(VPATH)/`$$i \
1 $(DESTDIR)$(includedir)/$$i; \
1 done
1
1 Automake does something similar. However the above hack works only
1 if the files listed in `HEADERS' are in the current directory or a
1 subdirectory; they should not be in an enclosing directory. If we had
1 `HEADERS = ../f.h', the above fragment would fail in a VPATH build with
1 Tru64 `make'. The reason is that not only does Tru64 `make' rewrite
1 dependencies, but it also simplifies them. Hence `../f.h' becomes
1 `../pkg/f.h' instead of `../pkg/src/../f.h'. This obviously defeats
1 any attempt to strip a leading `../pkg/src/' component.
1
1 The following example makes the behavior of Tru64 `make' more
1 apparent.
1
1 $ cat Makefile
1 VPATH = sub
1 all: ../foo
1 echo ../foo
1 $ ls
1 Makefile foo
1 $ make
1 echo foo
1 foo
1
1 Dependency `../foo' was found in `sub/../foo', but Tru64 `make'
1 simplified it as `foo'. (Note that the `sub/' directory does not even
1 exist, this just means that the simplification occurred before the file
1 was checked for.)
1
1 For the record here is how SunOS 4 `make' behaves on this example.
1
1 $ make
1 make: Fatal error: Don't know how to make target `../foo'
1 $ mkdir sub
1 $ make
1 echo sub/../foo
1 sub/../foo
1