The Basics: VPATH and vpath

[article]
Summary:

Ask Mr. Make talks about the uses of VPATH and vpath.

The Problem

As usual here's a simple Makefile that illustrates the use of VPATH and vpath.

SRCS := foo.c bar.c baz.c

OBJS := $(SRCS:.c=.o)

.PHONY: all

all: $(OBJS)

foo.o: foo_header.h

bar.o: string.h


Assume that the source files foo.c, bar.c and baz.c are all in the same directory as the Makefile.  When you run GNU Make you get an error:

$ make

cc    -c -o foo.o foo.c

make: *** No rule to make target `string.h', needed by `bar.o'.  Stop.


because string.h (which is a common header file) cannot be found.  There are a couple of solutions to this:

1. Change string.h to the full path where it is actually located.  For example, on my machine, its full path is /usr/include/string.h.

2. Use VPATH or vpath to find it.

The first solution is actually what happens if you use any sort of automatic dependency generation (like makedepend).  It will create dependencies containing the full path of #included header files.  But we could also use VPATH or vpath.

VPATH and vpath

The VPATH is a list of directories to be searched for missing source files (actually for missing prerequisites: they don't have to be source files, but VPATH and vpath are best only used for source files).

The list can be separated by spaces or colons (and on Windows versions of GNU Make it's possible to use semicolon as a list element separator): the best approach is probably to use spaces as this works across all platforms.

When GNU Make cannot find a prerequisite it will search the directories in the VPATH list (from first to last) and stop at the first directory in which it finds the missing prerequisite.   It then substitutes the location where the missing prerequisite is found for the name specified in the Makefile.

So, we can fix the broken Makefile above by specifying:

VPATH = /usr/include

anywhere in the Makefile.  Now a GNU Make works correctly:

$ make

cc    -c -o foo.o foo.c

cc    -c -o bar.o bar.c

cc    -c -o baz.o baz.c


If you want to clear the VPATH then simply set it to empty:

VPATH =

The problem with this approach is that VPATH is a hammer to crack a nut: it would have been better to tell GNU Make just where to find string.h.  Using this VPATH solution means that any time GNU Make can't find a prerequisite it'll go searching down the VPATH.   That can be a maintenance problem:

1. If there are files with the same name in different directories, VPATH could end up picking the wrong one.

2. The list of directories must be maintained in the correct order, and this order may need to be translated for other applications (e.g. for the compiler's #include search path). vpath enables you to minimize these problems by using a smaller hammer!  The vpath directive has both a search path and a pattern.  Only missing prerequisites matching the pattern are searched using the associated path.  So vpath makes it possible, for example, to specify just a path to search for header (.h) files:

vpath %.h /usr/include

The % is the wildcard and matches anything, so that vpath directive solves the problem of the missing string.h in the example Makefile.

The vpath syntax is a little more complicated than VPATH and has three forms:

1. vpath pattern path This sets the search path (colon or blank separated) for the pattern.

2. vpath pattern This clears the path for the specified pattern.

3. vpath Clears all vpath settings

Neither VPATH nor vpath were designed to find system headers (like string.h); they were created to solve the problem of mixing sources and binaries in the same directory.   In the sample  Makefile above each of the .c files is compiled to a .o file in the same directory.  That gets messy if there are a large number of files.

It might be more desirable to place all the source files in their own directory and have the objects go into a different one.   For example, we could move the source files to src/, put common (non-system) headers in include/ and modify the Makefile.  Here I'm using three vpath directives to find the sources, the headers and the system headers:

SRCS := foo.c bar.c baz.c

OBJS := $(SRCS:.c=.o)

.PHONY: all

all: $(OBJS)

foo.o: foo_header.h

bar.o: string.h

vpath %.h /usr/include

vpath %.h include

vpath %.c src


And this works correctly:

$ ls -R

Makefile    include        src

./include:

foo_header.h

./src:

bar.c    baz.c    foo.c

$ make

cc    -c -o foo.o src/foo.c

cc    -c -o bar.o src/bar.c

cc    -c -o baz.o src/baz.c


The sources have been found in src/ and then object files have been created in the current directory (where the Makefile was found).

There's one twist I overlooked in that: my foo.c was actually a dummy file containing nothing, but the Makefile specified that it included foo_header.h.  If I modify foo.c to actually include foo_header.h then I get an error:

$ make

cc    -c -o foo.o src/foo.c

src/foo.c:1:24: error: foo_header.h: No such file or directory

make: *** <foo.o>Error 1


Although GNU Make was able to find the header, the compiler had no idea about the include/ directory.  To fix that I need to modify CPPFLAGS to add an -I command-line option.  So I add:

CPPFLAGS += -I include

to the Makefile, and it works correctly:

$ make

cc  -I include  -c -o foo.o src/foo.c

cc  -I include  -c -o bar.o src/bar.c

cc  -I include  -c -o baz.o src/baz.c


Here's where you have to be careful: vpath is specifying one set of directories to search for headers, and the -I options are potentially specifying a different list.   To prevent odd errors where it's desirable to make sure that the same list is used.  To do that it's possible to do the following:

INCLUDE_DIRECTORIES = include

vpath %.h $(INCLUDE_DIRECTORIES)

CPPFLAGS += $(addprefix -I ,$(INCLUDE_DIRECTORIES))
As long as INCLUDE_DIRECTORIES is a space-separated list, this will both set up the vpath to find them in the Makefile and add the appropriate -I options so that the same list of directories is searched in the same order by GNU Make and the compiler.

One neat thing you can do with this 'source finding' ability is to separate output for different types of build.  For example, it's easy to have separate release and debug versions of a build just by having release/ and debug/ directories with the sources staying in one place.  This can be very handy because they'll probably be built with different compiler options and separating output means that GNU Make's standard 'up to date' mechanism will work correctly when switching between release and debug builds.  If you mix debug and release binaries in the same directory then you end up with a problem when rebuilding because it's possible to end up with a mixture of incompatible binaries; separating them is the best solution.

Here's the modified sample Makefile that can build release or debug versions in respective directories.  For the debug version I've added the -g option to the compiler (to get debugging information in the binaries) using a target-specific modification to CPPFLAGS.

.PHONY: all

ifneq ($(DEBUG),)

NAME := $(if $(filter yes,$(DEBUG)),debug,release)

all: ; $(MAKE) $(NAME) -C $(NAME) -f ../Makefile DEBUG=

else

SRCS := foo.c bar.c baz.c

OBJS := $(SRCS:.c=.o)

.PHONY: debug release

debug: all

debug: CPPFLAGS += -g

release: all

all: $(OBJS)

foo.o: foo_header.h

bar.o: string.h

vpath %.h /usr/include

vpath %.c ../src

INCLUDE_DIRECTORIES = ../include

vpath %.h $(INCLUDE_DIRECTORIES)

CPPFLAGS += $(addprefix -I ,$(INCLUDE_DIRECTORIES))

endif


The mode is controlled by setting the DEBUG variable on the command-line to yes or no.  If DEBUG is set then the first part of the Makefile is used (and it only contains a single all rule).  The first part first translates the setting of DEBUG into either debug or release and then executes a sub-Make in the appropriate directory.

For example, if you do make DEBUG=yes then the Makefile will

execute:

make debug -C debug -f ../Makefile DEBUG= That starts GNU Make in the debug sub-directory (that's the -C debug> part), using the same Makefile (the -f ../Makefile; the .. is necessary because this will be executed from debug/), clears the DEBUG variable and tells the sub-Make to run the debug rule.

When the Makefile is rerun the first part is ignored (because DEBUG was cleared) and the main part runs.  In this case the debug target is built.  It modifies CPPFLAGS and builds all.

Here's what happens when you do a debug build:

$ make DEBUG=yes

make debug -C debug -f ../Makefile DEBUG= cc  -I ../include -g  -c -o foo.o ../src/foo.c cc  -I ../include -g  -c -o bar.o ../src/bar.c cc  -I ../include -g  -c -o baz.o ../src/baz.c
And a release build:

$ make DEBUG=no

make release -C release -f ../Makefile DEBUG= cc  -I ../include  -c -o foo.o ../src/foo.c cc  -I ../include  -c -o bar.o ../src/bar.c cc  -I ../include  -c -o baz.o ../src/baz.c
In both cases the output has been separated into a directory with the sources coming from the same location.  For the debug build you can see that the -g option has been added to the compiler command-line.

<size=12pt>You don't need them

Most of the time you can completely avoid using VPATH and vpath.  For most dependencies you'll be using some automatic dependency search program (such as makedepend or gcc

- -MP
) and so you won't need to have GNU Make search for those files because the dependencies will be fully specified.

On the other hand, the separation of different binary types (debug/release, or different architectures) is very handy.  It can, however, be easily achieved without using the VPATH/vpath by explicitly setting the paths for sources and objects in the Makefile.  The only problem with doing this is that GNU Make's built-in pattern rules cannot be used (because they either assume source and binary are in the same directory, or they use a

VPATH/vpath search).   To make up for that you have to provide your own versions of those pattern rules.

Here's my sample Makefile rewritten to eliminate vpath entirely (I cheated slightly be omitting the automatic dependency generation part).  It still separates output into debug/ and release/ but uses explicit paths to do so, and it doesn't require the GNU Make restart that the previous solution used.

OBJDIR := $(if $(filter yes,$(DEBUG)),debug,release) SRCDIR := src

INCLUDE_DIRECTORIES = include

CPPFLAGS += $(addprefix -I ,$(INCLUDE_DIRECTORIES))

SRCS := foo.c bar.c baz.c

OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.c=.o)) SRCS := $(addprefix $(SRCDIR)/,$(SRCS))

all: $(OBJS)

ifeq ($(OBJDIR),debug)

all: CPPFLAGS += -g

endif

$(OBJDIR)/%.o: $(SRCDIR)/%.c

~    $(COMPILE.c) $(OUTPUT_OPTION) $<


If you don't know the commands used by a built-in rule you can type make -p to get GNU Make to print them out.  For this rewrite I used the definition of the %.o: %.c rule:

%.o : %.c

~    $(COMPILE.c) $(OUTPUT_OPTION) $<


This Makefile is much simpler than the equivalent vpath version and it's easy to debug.  The locations of sources and objects is easy to find and change, and there's no risk of a vpath search doing the wrong thing.


 

User Comments

2 comments
Debra Lee's picture

On the other hand, it is very helpful to be able to separate different binary types (like debug/release or different architectures). But it's easy to do without the VPATH/vpath by setting the paths for sources and objects directly in the Makefile.

 
 

October 10, 2022 - 10:34pm
Lisa Sarah's picture

 Thank you for sharing a very meaningful article, I think it will be very helpful for me and everyone online.

November 9, 2023 - 11:45am

About the author

AgileConnection is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.