Make itself suffers a great number of limitations, only a few of which being listed here. First of all, remember that since commands are executed by the shell, all its weaknesses are inherited…
posix says that the $ construct in makefiles can be used only in inference rules and in the .DEFAULT rule; its meaning in ordinary rules is unspecified. Solaris 8's Make for instance will replace it with the argument.
Some Makes don't support leading underscores in macro names, such as on NEWS-OS 4.2R.
$ cat Makefile _am_include = # _am_quote = all:; @echo this is test $ make Make: Must be a separator on rules line 2. Stop. $ cat Makefile2 am_include = # am_quote = all:; @echo this is test $ make -f Makefile2 this is test
A command-line variable definition such as foo=bar overrides any definition of foo in the Makefile. Some Make implementations (such as gnu Make) will propagate this override to sub-invocations of make, this is allowed but not required by posix.
% cat Makefile foo = foo one: @echo $(foo) $(MAKE) two two: @echo $(foo) % make foo=bar # GNU make 3.79.1 bar make two make[1]: Entering directory `/home/adl' bar make[1]: Leaving directory `/home/adl' % pmake foo=bar # BSD make bar pmake two foo
You have a few possibilities if you do want the foo=bar override to propagate to sub-makes. One is to use the -e option, which causes all environment variables to have precedence over the Makefile macro definitions, and declare foo as an environment variable:
% env foo=bar make -e
The -e option is propagated to sub-makes automatically, and since the environment is inherited between make invocations, the foo macro will be overridden in sub-makes as expected.
Using -e could have unexpected side-effects if your environment contains some other macros usually defined by the Makefile. (See also the note about make -e and SHELL below.)
Another way to propagate overrides to sub-makes is to do it manually, from your Makefile:
foo = foo one: @echo $(foo) $(MAKE) foo=$(foo) two two: @echo $(foo)
You need to foresee all macros that a user might want to override if you do that.
POSIX Makes internally use the $(SHELL) macro to spawn shell processes and execute Makefile rules. This is a built-in macro supplied by Make, but it can be modified from the Makefile or a command-line argument.
Not all Makes will define this SHELL macro. OSF/Tru64 Make is an example; this implementation will always use /bin/sh. So it's a good idea to always define SHELL in your Makefiles. If you use Autoconf, do
SHELL = @SHELL@
posix-compliant makes should never acquire the value of $(SHELL) from the environment, even when make -e is used (otherwise, think about what would happen to your rules if SHELL=/bin/tcsh).
However not all Make implementations will make this exception. For instance it's not surprising that OSF/Tru64 Make doesn't protect SHELL, since it doesn't use it.
% cat Makefile SHELL = /bin/sh FOO = foo all: @echo $(SHELL) @echo $(FOO) % env SHELL=/bin/tcsh FOO=bar make -e # OSF1 V4.0 Make /bin/tcsh bar % env SHELL=/bin/tcsh FOO=bar gmake -e # GNU make /bin/sh bar
Some Makes treat anything starting with a tab as a command for the current rule, even if the tab is immediately followed by a #. The Make from Tru64 Unix V5.1 is one of them. The following Makefile will run # foo through the shell.
all: # foo
There is no VPATH support specified in posix. Many Makes have a form of VPATH support, but its implementation is not consistent amongst Makes.
Maybe the best suggestion to give to people who need the VPATH feature is to choose a Make implementation and stick to it. Since the resulting Makefiles are not portable anyway, better choose a portable Make (hint, hint).
Here are a couple of known issues with some VPATH implementations.
Any assignment to VPATH causes Sun Make to only execute the first set of double-colon rules. (This comment has been here since 1994 and the context has been lost. It's probably about SunOS 4. If you can reproduce this, please send us a test case for illustration.)
An implementation of make would not prefix $ if this prerequisite has been found in a VPATH dir. This means that
VPATH = ../src .c.o: cc -c $ -o $
would run cc -c foo.c -o foo.o, even if foo.c was actually found in ../src/.
This can be fixed as follows.
VPATH = ../src .c.o: cc -c `test -f $ || echo ../src/`$ -o $
This kludge was introduced in Automake in 2000, but the exact context have been lost. If you know which make implementation is involved here, please drop us a note.
As said elsewhere, using $ in explicit rules is not portable. The prerequisite file must be named explicitly in the rule. If you want to find the prerequisite via a VPATH search, you have to code the whole thing manually. For instance, using the same pattern as above:
VPATH = ../src foo.o: foo.c cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
Some Make implementations, such as SunOS Make, will search prerequisites in VPATH and rewrite all their occurrences in the rule appropriately.
For instance
VPATH = ../src foo.o: foo.c cc -c foo.c -o foo.o
would execute cc -c ../src/foo.c -o foo.o if foo.c was found in ../src. That sounds great.
However, for the sake of other Make implementations, we can't rely on this, and we have to search VPATH manually:
VPATH = ../src foo.o: foo.c cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
However the "prerequisite rewriting" still applies here. So if foo.c is in ../src, SunOS Make will execute
cc -c `test -f ../src/foo.c || echo ../src/`foo.c -o foo.o
which reduces to
cc -c foo.c -o foo.o
and thus fails. Oops.
One workaround is to make sure that foo.c never appears as a plain word in the rule. For instance these three rules would be safe.
VPATH = ../src foo.o: foo.c cc -c `test -f ./foo.c || echo ../src/`foo.c -o foo.o foo2.o: foo2.c cc -c `test -f 'foo2.c' || echo ../src/`foo2.c -o foo2.o foo3.o: foo3.c cc -c `test -f "foo3.c" || echo ../src/`foo3.c -o foo3.o
Things get worse when your prerequisites are in a macro.
VPATH = ../src HEADERS = foo.h foo2.h foo3.h install-HEADERS: $(HEADERS) for i in $(HEADERS); do \ $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \ $(DESTDIR)$(includedir)/$$i; \ done
The above install-HEADERS rule is not sun-proof because for i in $(HEADERS); will be expanded as for i in foo.h foo2.h foo3.h; where foo.h and foo2.h are plain words and are hence subject to VPATH adjustments.
If the three files are in ../src, the rule is run as:
for i in ../src/foo.h ../src/foo2.h foo3.h; do \ install -m 644 `test -f $i || echo ../src/`$i \ /usr/local/include/$i; \ done
where the two first install calls will fail. For instance, consider the foo.h installation:
install -m 644 `test -f ../src/foo.h || echo ../src/`../src/foo.h \ /usr/local/include/../src/foo.h;
It reduces to:
install -m 644 ../src/foo.h /usr/local/include/../src/foo.h;
Note that the manual VPATH search did not cause any problems here; however this command installs foo.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 ../src/`$$i \ $(DESTDIR)$(includedir)/$$i; \ done
Indeed, headers='$(HEADERS)' expands to headers='foo.h foo2.h foo3.h' where foo2.h is still a plain word. (Aside: the headers='$(HEADERS)'; for i in $$headers; idiom is a good idea if $(HEADERS) can be empty, because some shell produce a syntax error on for i in;.)
One workaround is to strip this unwanted ../src/ prefix manually:
VPATH = ../src HEADERS = foo.h foo2.h foo3.h install-HEADERS: $(HEADERS) headers='$(HEADERS)'; for i in $$headers; do \ i=`expr "$$i" : '../src/\(.*\)'`; $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \ $(DESTDIR)$(includedir)/$$i; \ done
When a prerequisite is a sub-directory of VPATH, Tru64 Make will create it in the current directory.
% mkdir -p foo/bar build % cd build % cat Makefile END VPATH = .. all: foo/bar END % make mkdir foo mkdir foo/bar
This can yield unexpected results if a rule uses a manual VPATH search as presented before.
VPATH = .. all : foo/bar command `test -d foo/bar || echo ../`foo/bar
The above command will be run on the empty foo/bar directory that was created in the current directory.