Follow these steps:
Create a makefile in yet another directory. This makefile can be used to build your application, but only after the makefiles in step 1 have been executed. Give this makefile a phony target
allwhose prerequisite is your executable. Declare a target for your executable with prerequisites consisting of the libraries which your application uses, together with the object files to be built from your application’s .cpp files. Write a command script to build the executable from the collection libraries and object files, as described in Recipe 1.5. If necessary, write a pattern rule to generate object files from .cpp files, as shown in Recipe 1.16. Add
cleantargets, as shown in Recipe 1.15, and machinery to automatically generate source file dependencies, as shown in Recipe 1.16.
Create a makefile in a directory which is an ancestor of the directories containing all the other makefiles — let’s call the new makefile the top-level makefile and the others the subordinate makefiles. Declare a default target
allwhose prerequisite is the directory containing the makefile created in step 2. Declare a rule whose targets consists of the directories containing the subordinate makefiles, and whose command script invokes make in each target directory with a target specified as the value of the variable
TARGET. Finally, declare targets specifying the dependencies between the default targets of the subordinate makefiles.
Example 1-23. Makefile for hellobeatles.exe using GCC
# Specify the source files, target files, the build directories, # and the install directory SOURCES = hellobeatles.cpp OUTPUTFILE = hellobeatles LIBJOHNPAUL = libjohnpaul.a LIBGEORGERINGO = libgeorgeringo.so JOHNPAULDIR = ../johnpaul GEORGERINGODIR = ../georgeringo INSTALLDIR = ../binaries # # Add the parent directory as an include path # CPPFLAGS += -I.. # # Default target # .PHONY: all all: $(HELLOBEATLES) # # Target to build the executable. # $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) \ $(JOHNPAULDIR)/$(LIBJOHNPAUL) \ $(GEORGERINGODIR)/$(LIBGEORGERINGO) $(CXX) $(LDFLAGS) -o $@ $^ .PHONY: install install: mkdir -p $(INSTALLDIR) cp -p $(OUTPUTFILE) $(INSTALLDIR) .PHONY: clean clean: rm -f *.o rm -f $(OUTPUTFILE) # Generate dependencies of .ccp files on .hpp files include $(subst .cpp,.d,$(SOURCES)) %.d: %.cpp $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
Next, create a top-level makefile in the directory containing johnpaul, georgeringo, hellobeatles, and binaries, as shown in Example 1-24.
# All the targets in this makefile are phony .PHONY: all johnpaul georgeringo hellobeatles # Default target all: hellobeatles # The targets johnpaul, georgeringo, and hellobeatles represent # directories; the command script invokes make in each directory johnpaul georgeringo hellobeatles: $(MAKE) --directory=$@ $(TARGET) # This rule indicates that the default target of the makefile # in directory hellobeatles depends on the default targets of # the makefiles in the directories johnpaul and georgeringo .PHONY: hellobeatles hellobeatles: johnpaul georgeringo
To build hellobeatles, change to the directory
containing the top-level makefile, and enter
To copy the files libjohnpaul.a, libgeorgeringo.so, and hellobeatles to the directory binaries,
TARGET=install. To clean the project, enter
The approach to managing complex projects demonstrated in this recipe is known as recursive make. It allows you to organize a project into a collection of modules, each with its own makefile, and to specify the dependencies between the modules. It’s not limited to a single top-level makefile with a collection of child makefiles: the technique can be extended to handle multi-level tree structures. While recursive make was once the standard technique for managing large projects with make, there are other methods which are now considered superior. For details, refer once again to Managing Projects with GNU make, Third Edition, by Robert Mecklenburg (O’Reilly).
Example 1-23 is a straightforward application of the techniques demonstrated in Recipe 1.15, Recipe 1.16, and Recipe 1.17. There’s really just one interesting point. As illustrated in Recipe 1.15, when compiling hellobeatles.cpp from the command line it’s necessary to use the option -I.. so that the compiler can find the headers johnpaul.hpp and georgeringo.hpp. One solution would be to write an explicit rule for building hellobeatles.o with a command script containing the option -I.., like so:
hellobeatles.o: hello.beatles.cpp g++ -c -I.. -o hellobeatles.o hellobeatles.cpp
Instead, I’ve taken advantage of the customization point
CPPFLAGS, described in Recipe
1.15, to specify that whenever an object file is compiled from a .cpp file, the option -I..
should be added to the command-line:
CPPFLAGS += -I..
I’ve used the assignment operator
+=, instead of
=, so that the effect will be cumulative with
whatever value of
CPPFLAGS may have been specified on
the command line or in the environment.
Now let’s look at how Example 1-24 works. The most important rule is the one which causes make to be invoked in each of the directories johnpaul, georgeringo, and hellobeatles:
johnpaul georgeringo hellobeatles: $(MAKE) --directory=$@ $(TARGET)
To understand this rule, you need to know three things. First, the variable
MAKE expands to the name of the currently running instance of
make. Usually this will be
make, but on some systems it could be
gmake. Second, the command line option —directory=
<path> causes make to be invoked with
<path> as its
current directory. Third, a rule with several targets is equivalent to a collection of
rules, each having one target, and having identical command scripts. So the above rule is
johnpaul: $(MAKE) --directory=$@ $(TARGET) georgeringo: $(MAKE) --directory=$@ $(TARGET) hellobeatles: $(MAKE) --directory=$@ $(TARGET)
This in turn is equivalent to:
johnpaul: $(MAKE) --directory=johnpaul $(TARGET) georgeringo: $(MAKE) --directory=georgeringo $(TARGET) hellobeatles: $(MAKE) --directory=hellobeatles $(TARGET)
The effect of the rule, therefore, is to invoke the makefiles in each of the
directories johnpaul, georgeringo, and hellobeatles, with the
value of the variable
TARGET tacked onto the command
line. As a result, you can build target
xxx of each of the
makefiles by executing the top-level makefile with the option TARGET=
The final rule of the makefile ensures that the subordinate makefiles are executed in
the correct order; it simply declares that the target
hellobeatles depends on the targets
hellobeatles: johnpaul georgeringo
In a more complex application, there might be many dependencies between the executable and its component libraries. For each such component, declare a rule indicating the other components on which it directly depends.