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