Suppose, for example, that we have a project which has separate UDP and TCP sections. We would like to keep the code for each of these sections in different subdirectories like so:
---udp / project< \ ---tcpWe will put a makefile in each of the directories: project, udp, and tcp. The job of the top-level makefile (the one in "project") is to direct the operation of the lower-level makefiles. Their job is to build the udp and tcp versions of the project. The top makefile might look like this:
all:
(cd udp; make all)
(cd tcp; make all)
install:
(cd udp; make install)
(cd tcp; make install)
clean:
(cd udp; make clean)
(cd tcp; make clean)
The first command for the target "all", starts a subshell which goes into the
"udp" subdirectory then runs "make all
". That "make" will build
the UDP version then return to the top-level makefile. The second command for
"all" then starts a subshell which goes into the "tcp" subdirectory and runs
"make all
". This "make" will build the TCP version of the project
before returning to the top-level makefile. How the UDP and TCP versions are
actually built are hidden from the top-level makefile, and can be changed
without changing the top-level makefile. (Except that they must provide the
targets used by this makefile.)
The makefile for the UDP version might look like this:
INSTDIR=/usr/local/bin
all: server client
server: server.c
gcc -Wall -o server server.c
client: client.c
gcc -Wall -o client client.c
install: server client
mv server client ${INSTDIR}
clean:
-rm -f server.o client.o core
When the top-level makefile runs (cd udp; make all)
, this makefile
will build server and client in the udp
subdirectory. Running (cd udp; make install)
, from the top level
makefile will cause this makefile to move the executables to /usr/local/bin.
Also, running (cd udp; make clean)
from the top-level makefile
will cause the objects and a possible core file to be removed from the udp
subdirectory.
Another reason for using multi-level makefiles is to isolate user-customizable variable settings to the top-level makefile. This lets the user work exclusively with the top-level makefile, rather than having to go into each subdirectoy to change variable values. The top-level makefile then passes variable values to the lower-level makefiles which need them.
The UDP makefile above appears to violate this idea because it sets INSTDIR explicitly, but a variable setting from the command line takes precedence over a setting in the makefile. By changing the "install" rule in the top-level makefile to pass a value for INSTDIR to one or both of the lower-level makefiles, we can isolate the customization to the top level:
all:
(cd udp; make all)
(cd tcp; make all)
install:
(cd udp; make install INSTDIR=/usr/local/bin)
(cd tcp; make install INSTDIR=/usr/local/bin)
clean:
(cd udp; make clean)
(cd tcp; make clean)
Now the user just hase to change the values passed as INSTDIR. We can tighten
this a little by using a variable at the top level:
INSTDIR=/usr/bin
all:
(cd udp; make all)
(cd tcp; make all)
install:
(cd udp; make install INSTDIR=${INSTDIR})
(cd tcp; make install INSTDIR=${INSTDIR})
clean:
(cd udp; make clean)
(cd tcp; make clean)
Note that the commands in the "install" rule are interpreted as:
(cd udp; make install INSTDIR=/usr/bin)
(cd tcp; make install INSTDIR=/usr/bin)
These commands will run "make" in each of the subdirectories and use "/usr/bin"
as INSTDIR in each, even if INSTDIR is set in the lower-level makefile. If
we run:
(cd udp; make install)
"make" will use INSTDIR as defined in "udp/makefile" because it is not
overridden by the top-level makefile. Also, if we go into the udp subdirectory
and run make from there (without using the top-level makefile), INSTDIR will
be defined as in "udp/makefile". This is one reason that running lower-level
makefiles by hand should be discouraged.