Previous: Limitations of Usual Tools, Up: Portable Shell


10.11 Limitations of Make

make itself suffers a great number of limitations, only a few of which are 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.
Leading underscore in macro names
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
     

Trailing backslash in macro
On some versions of HP-UX, make will read multiple newlines following a backslash, continuing to the next non-empty line. For example,
          FOO = one \
          
          BAR = two
          
          test:
                  : FOO is "$(FOO)"
                  : BAR is "$(BAR)"
     

shows FOO equal to one BAR = two. Other makes sensibly let a backslash continue only to the immediately following line.

Escaped newline in comments
According to POSIX, Makefile comments start with # and continue until an unescaped newline is reached.
          % cat Makefile
          # A = foo \
                bar \
                baz
          
          all:
                  @echo ok
          % make   # GNU make
          ok
     

However in Real World this is not always the case. Some implementations discards anything from # up to the end of line, ignoring any trailing backslash.

          % pmake  # BSD make
          "Makefile", line 3: Need an operator
          Fatal errors encountered -- cannot continue
     

Therefore, if you want to comment out a multi-line definition, prefix each line with #, not only the first.

          # A = foo \
          #     bar \
          #     baz
     

make macro=value and sub-makes.
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. Some other implementation will not pass the substitution along to sub-makes.
          % 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.

This syntax (foo=bar make -e) is portable only when used outside a Makefile, for instance from a script or from the command line. When run inside a make rule, GNU make 3.80 and prior versions forget to propagate the -e option to sub-makes.

Moreover, 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.

The SHELL macro
POSIX-compliant makes internally use the $(SHELL) macro to spawn shell processes and execute Makefile rules. This is a builtin 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
     

Comments in rules
Never put comments in a rule.

Some make 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
     

The obj/ subdirectory.
Never name one of your subdirectories obj/ if you don't like surprises.

If an obj/ directory exists, BSD make will enter it before reading Makefile. Hence the Makefile in the current directory will not be read.

          % cat Makefile
          all:
                  echo Hello
          % cat obj/Makefile
          all:
                  echo World
          % make      # GNU make
          echo Hello
          Hello
          % pmake     # BSD make
          echo World
          World
     

make -k
Do not rely on the exit status of make -k. Some implementations reflect whether they encountered an error in their exit status; other implementations always succeed.
          % cat Makefile
          all:
                  false
          % make -k; echo exit status: $?    # GNU make
          false
          make: *** [all] Error 1
          exit status: 2
          % pmake -k; echo exit status: $?   # BSD make
          false
          *** Error code 1 (continuing)
          exit status: 0
     

VPATH
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.

VPATH and double-colon rules
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.)
$< not supported in explicit rules
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 following pattern:
               VPATH = ../pkg/src
               foo.c: ifoo.c
                       cp `test -f ifoo.c || echo ../pkg/src/`ifoo.c foo.c
          

Automatic rule rewriting
Some make implementations, such as SunOS make or OSF1/Tru64 make, will search prerequisites in VPATH and rewrite all their occurrences in the rule appropriately.

For instance

               VPATH = ../pkg/src
               foo.c: ifoo.c
                       cp ifoo.c foo.c
          

would execute cp ../pkg/src/ifoo.c foo.c if ifoo.c was found in ../pkg/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 = ../pkg/src
               foo.c: ifoo.c
                       cp `test -f ifoo.c || echo ../pkg/src/`ifoo.c foo.c
          

However the "prerequisite rewriting" still applies here. So if ifoo.c is in ../pkg/src, SunOS make and OSF1/Tru64 make will execute

               cp `test -f ../pkg/src/ifoo.c || echo ../pkg/src/`ifoo.c foo.c
          

which reduces to

               cp ifoo.c foo.c
          

and thus fails. Oops.

One workaround is to make sure that ifoo.c never appears as a plain word in the rule. For instance these three rules would be safe.

               VPATH = ../pkg/src
               foo.c: ifoo.c
                       cp `test -f ./ifoo.c || echo ../pkg/src/`ifoo.c foo.c
               foo2.c: ifoo2.c
                       cp `test -f 'ifoo2.c' || echo ../pkg/src/`ifoo2.c foo2.c
               foo3.c: ifoo3.c
                       cp `test -f "ifoo3.c" || echo ../pkg/src/`ifoo3.c foo3.c
          

Things get worse when your prerequisites are in a macro.

               VPATH = ../pkg/src
               HEADERS = foo.h foo2.h foo3.h
               install-HEADERS: $(HEADERS)
                       for i in $(HEADERS); do \
                         $(INSTALL) -m 644 `test -f $$i || echo ../pkg/src/`$$i \
                           $(DESTDIR)$(includedir)/$$i; \
                       done
          

The above install-HEADERS rule is not SunOS-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 ../pkg/src, the rule is run as:

               for i in ../pkg/src/foo.h ../pkg/src/foo2.h foo3.h; do \
                 install -m 644 `test -f $i || echo ../pkg/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 ../pkg/src/foo.h || echo ../pkg/src/`../pkg/src/foo.h \
                 /usr/local/include/../pkg/src/foo.h;
          

It reduces to:

               install -m 644 ../pkg/src/foo.h /usr/local/include/../pkg/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 ../pkg/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 shells diagnose a syntax error on for i in;.)

One workaround is to strip this unwanted ../pkg/src/ prefix manually:

               VPATH = ../pkg/src
               HEADERS = foo.h foo2.h foo3.h
               install-HEADERS: $(HEADERS)
                       headers='$(HEADERS)'; for i in $$headers; do \
                         i=`expr "$$i" : '../pkg/src/\(.*\)'`;
                         $(INSTALL) -m 644 `test -f $$i || echo ../pkg/src/`$$i \
                           $(DESTDIR)$(includedir)/$$i; \
                       done
          

Automake does something similar. However the above hack works only if the files listed in HEADERS are in the current directory or a subdirectory; they should not be in an enclosing directory. If we had HEADERS = ../foo.h, the above fragment would fail in a VPATH build with OSF1/Tru64 make. The reason is that not only does OSF1/Tru64 make rewrite dependencies, but it also simplifies them. Hence ../foo.h will become ../pkg/foo.h instead of ../pkg/src/../foo.h. This obviously defeats any attempt to strip a leading ../pkg/src/ component.

The following example makes the behavior of OSF1/Tru64 make more apparent.

               % cat Makefile
               VPATH = sub
               all: ../foo
                       echo ../foo
               % ls
               Makefile foo
               % make
               echo foo
               foo
          

Dependency ../foo was found in sub/../foo, but OSF1/Tru64 make simplified it as foo. (Note that the sub/ directory does not even exist, this just means that the simplification occurred before the file was checked for.)

For the records here is how SunOS make behaves on this very same example.

               % make
               make: Fatal error: Don't know how to make target `../foo'
               % mkdir sub
               % make
               echo sub/../foo
               sub/../foo
          

OSF/Tru64 make creates prerequisite directories magically
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.

target lookup
GNU make uses a rather complex algorithm to decide when it should use files found via a VPATH search. See How Directory Searches are Performed.

If a target needs to be rebuilt, GNU make discards the filename found during the VPATH search for this target, and builds the file locally using the filename given in the Makefile. If a target does not need to be rebuilt, GNU make uses the filename found during the VPATH search.

Other make implementations, like NetBSD make, are easier to describe: the filename found during the VPATH search will be used whether the target needs to be rebuilt or not. Therefore new files are created locally, but existing files are updated at their VPATH location.

OpenBSD and FreeBSD makes, however, will never perform a VPATH search for a dependency which has an explicit rule. This is extremely annoying.

When attempting a VPATH build for an autoconfiscated package (e.g,, mkdir build && cd build && ../configure), this means the GNU make will build everything locally in the build directory, while BSD make will build new files locally and update existing files in the source directory.

               % cat Makefile
               VPATH = ..
               all: foo.x bar.x
               foo.x bar.x: newer.x
                       @echo Building $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               Building foo.x
               Building bar.x
               % pmake       # NetBSD make
               Building foo.x
               Building ../bar.x
               % fmake       # FreeBSD make, OpenBSD make
               Building foo.x
               Building bar.x
               % tmake       # Tru64 make
               Building foo.x
               Building bar.x
               % touch ../bar.x
               % make        # GNU make
               Building foo.x
               % pmake       # NetBSD make
               Building foo.x
               % fmake       # FreeBSD make, OpenBSD make
               Building foo.x
               Building bar.x
               % tmake       # Tru64 make
               Building foo.x
               Building bar.x
          

Note how NetBSD make updates ../bar.x in its VPATH location, and how FreeBSD, OpenBSD, and Tru64 make always update bar.x, even when ../bar.x is up to date.

Another point worth mentioning is that once GNU make has decided to ignore a VPATH filename (e.g., it ignored ../bar.x in the above example) it will continue to ignore it when the target occurs as a prerequisite of another rule.

The following example shows that GNU make does not look up bar.x in VPATH before performing the .x.y rule, because it ignored the VPATH result of bar.x while running the bar.x: newer.x rule.

               % cat Makefile
               VPATH = ..
               all: bar.y
               bar.x: newer.x
                       @echo Building $@
               .SUFFIXES: .x .y
               .x.y:
                       cp $< $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               Building bar.x
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               make: *** [bar.y] Error 1
               % pmake       # NetBSD make
               Building ../bar.x
               cp ../bar.x bar.y
               % rm bar.y
               % fmake       # FreeBSD make, OpenBSD make
               echo Building bar.x
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               *** Error code 1
               % tmake       # Tru64 make
               Building bar.x
               cp: bar.x: No such file or directory
               *** Exit 1
          

Note that if you drop away the command from the bar.x: newer.x rule, GNU make will magically start to work: it knows that bar.x hasn't been updated, therefore it doesn't discard the result from VPATH (../bar.x) in succeeding uses. Tru64 will also work, but FreeBSD and OpenBSD still don't.

               % cat Makefile
               VPATH = ..
               all: bar.y
               bar.x: newer.x
               .SUFFIXES: .x .y
               .x.y:
                       cp $< $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               cp ../bar.x bar.y
               % rm bar.y
               % pmake       # NetBSD make
               cp ../bar.x bar.y
               % rm bar.y
               % fmake       # FreeBSD make, OpenBSD make
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               *** Error code 1
               % tmake       # True64 make
               cp ../bar.x bar.y
          

It seems the sole solution that would please every make implementation is to never rely on VPATH searches for targets. In other words, VPATH should be reserved to unbuilt sources.


Single Suffix Rules and Separated Dependencies
A Single Suffix Rule is basically a usual suffix (inference) rule (`.from.to:'), but which destination suffix is empty (`.from:').

Separated dependencies simply refers to listing the prerequisite of a target, without defining a rule. Usually one can list on the one hand side, the rules, and on the other hand side, the dependencies.

Solaris make does not support separated dependencies for targets defined by single suffix rules:

          $ cat Makefile
          .SUFFIXES: .in
          foo: foo.in
          .in:
                  cp $< $ $ touch foo.in
          $ make
          $ ls
          Makefile  foo.in
     

while GNU Make does:

          $ gmake
          cp foo.in foo
          $ ls
          Makefile  foo       foo.in
     

Note it works without the `foo: foo.in' dependency.

          $ cat Makefile
          .SUFFIXES: .in
          .in:
                  cp $< $ $ make foo
          cp foo.in foo
     

and it works with double suffix inference rules:

          $ cat Makefile
          foo.out: foo.in
          .SUFFIXES: .in .out
          .in.out:
                  cp $< $ $ make
          cp foo.in foo.out
     

As a result, in such a case, you have to write target rules.

Timestamp Resolution
Traditionally, file timestamps had 1-second resolution, and make used those timestamps to determine whether one file was newer than the other. However, many modern filesystems have timestamps with 1-nanosecond resolution. Some make implementations look at the entire timestamp; others ignore the fractional part, which can lead to incorrect results. Normally this is not a problem, but in some extreme cases you may need to use tricks like `sleep 1' to work around timestamp truncation bugs.

Commands like `cp -p' and `touch -r' typically do not copy file timestamps to their full resolutions (see Limitations of Usual Tools). Hence you should be wary of rules like this:

          dest: src
                  cp -p src dest
     

as dest will often appear to be older than src after the timestamp is truncated, and this can cause make to do needless rework the next time it is invoked. To work around this problem, you can use a timestamp file, e.g.:

          dest-stamp: src
                  cp -p src dest
                  date >dest-stamp