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)
vpath %.h /usr/include
vpath %.h include
vpath %.c src
And this works correctly:
$ ls -R
Makefile include src
bar.c baz.c foo.c
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:
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:
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.
NAME := $(if $(filter yes,$(DEBUG)),debug,release)
all: ; $(MAKE) $(NAME) -C