You want to use GNU make to build a static library from a collection of C++ source files, such as those listed in Example 1-1.
First, create a makefile in the directory where you want your static library to be
created, and declare a phony target all
whose single
prerequisite is the static library. Next, declare your static library target. Its
prerequisites should be the object files that the library will contain, and its command
script should be a command line to build the library from the collection of object files,
as demonstrated in Recipe 1.3. If you
are using GCC or a compiler with similar command-line syntax, customize the implicit
patterns rules, if necessary, by modifying one or more of the variables CXX
, CXXFLAGS
,
etc. used in make's database
of implicit rules, as shown in Recipe
1.15. Otherwise, write a
pattern rule telling make how to compile
.cpp files into object files, using the command
lines from Table 1-4 and the pattern
rule syntax explained in Recipe 1.16.
Next, declare targets indicating how each of your library’s source files depends on the
headers it includes, directly or indirectly. You can write these dependencies by hand or
arrange for them to be generated automatically. Finally, add install
and clean
targets as demonstrated
in Recipe 1.15.
For example, to build a static library from the source files listed in Example 1-2 using GCC on Unix, create a makefile in the directory johnpaul, as shown in Example 1-20.
Example 1-20. Makefile for libjohnpaul.a using GCC on Unix
# Specify extensions of files to delete when cleaning CLEANEXTS = o a # Specify the target file and the install directory OUTPUTFILE = libjohnpaul.a INSTALLDIR = ../binaries # Default target .PHONY: all all: $(OUTPUTFILE) # Build libjohnpaul.a from john.o, paul.o, and johnpaul.o $(OUTPUTFILE): john.o paul.o johnpaul.o ar ru $@ $^ ranlib $@ # No rule to build john.o, paul.o, and johnpaul.o from .cpp # files is required; this is handled by make's database of # implicit rules .PHONY: install install: mkdir -p $(INSTALLDIR) cp -p $(OUTPUTFILE) $(INSTALLDIR) .PHONY: clean clean: for file in $(CLEANEXTS); do rm -f *.$$file; done # Indicate dependencies of .ccp files on .hpp files john.o: john.hpp paul.o: paul.hpp johnpaul.o: john.hpp paul.hpp johnpaul.hpp
Similarly, to build a static library using Visual C++, your makefile might look as shown in Example 1-21.
Example 1-21. Makefile for libjohnpaul.lib using Visual C++
# Specify extensions of files to delete when cleaning CLEANEXTS = obj lib # Specify the target file and the install directory OUTPUTFILE = libjohnpaul.lib INSTALLDIR = ../binaries # Pattern rule to build an object file from a .cpp file %.obj: %.cpp "$(MSVCDIR)/bin/cl" -c -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t \ $(CXXFLAGS) $(CPPFLAGS) -Fo"$@" $< # Default target .PHONY: all all: $(OUTPUTFILE) # Build libjohnpaul.lib from john. obj, paul. obj, and johnpaul. obj $(OUTPUTFILE): john.obj paul.obj johnpaul.obj "$(MSVCDIR)/bin/link" -lib -nologo -out:"$@" $^ .PHONY: install install: mkdir -p $(INSTALLDIR) cp -p $(OUTPUTFILE) $(INSTALLDIR) .PHONY: clean clean: for file in $(CLEANEXTS); do rm -f *.$$file; done # Indicate dependency of .cpp files on .hpp files john.obj: john.hpp paul.obj: paul.hpp johnpaul. obj: john.hpp paul.hpp johnpaul.hpp
Tip
In Example 1-21, I’ve expressed
Visual C++’s link.exe command as "$(MSVCDIR)/bin/link
“, using the environment variable
MSVCDIR
set by vcvars32.bat. This prevents confusion between the Visual C++ linker and
the Unix link command, supported by Cygwin and
MSYS. For consistency, I’ve also expressed Visual C++’s compile command using MSVCDIR
.
Let’s walk through Example 1-20. I
start by defining variables to represent the output file, the install directory, and the
extensions of files that should be deleted when the target clean
is built. Next, I declare the default target all
, as in Example
1-14.
The rule to build the static library looks like this:
$(OUTPUTFILE): john.o paul.o johnpaul.o ar ru $@ $^ ranlib $@
It’s a straightforward adaptation of the entry for GCC in Table 1-10. Here $(OUTPUTFILE)
and $@
both expand to
libjohnpaul.a
, and $^
expands to the list of prerequisites john.o
paul.o johnpaul.o
.
The next two rules declare install
and clean
targets, as in Recipe 1.15. The only difference is that in
Example 1-20 I use a shell looping
construct to remove all files whose extension appears in the list o a
— i.e., all object or static library files:
for file in $(CLEANEXTS); do rm -f *.$$file; done
I’ve used a double dollar sign to prevent make
from expanding the variable $$file
rather than passing
it on to the shell.
The last three rules specify the dependency relationships between the library’s .cpp files and the headers they include. There’s one rule for each .cpp file; its target is the object file to be built from the .cpp file, and its prerequisites are the header files included—directly or indirectly—by the .cpp file:
john.o: john.hpp paul.o: paul.hpp johnpaul.o: john.hpp paul.hpp johnpaul.hpp
This can be understood as follows. If a .cpp file includes a header file—directly or indirectly—it must be rebuilt each time the header is modified. However, since the .cpp file exists and does not appear as the target of any rule, it is never out of date, as discussed in Recipe Recipe 1.15. Consequently, when the header is modified, no recompilation is triggered. The fix is to declare a rule making these dependencies explicit; whenever one of the headers in question is modified, the object file corresponding to the .cpp will become out of date, causing the .cpp file to be recompiled.
This solution is only adequate for very small projects, since it’s extremely difficult to keep the targets representing source file dependencies synchronized with a changing codebase. Fortunately, there are several methods for generating these dependencies automatically. For example, you can replace the last three rules in Example 1-20 with the following:
# Generate dependencies of .ccp files on .hpp files include john.o paul.o johnpaul.o %.d: %.cpp $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
This bit of code relies on the compiler option -M which causes GCC to output dependency information for inclusion in a makefile. For a detailed explanation of how it works—and why it’s sometimes inadequate—see Managing Projects with GNU make, Third Edition, by Robert Mecklenburg (O’Reilly).
This method can be adapted to work with most toolsets, since most compilers provide an option similar to GCC’s -M; in fact, the option is usually either -M or -m. Visual C++, however, does not provide an option for generating makefile dependencies. If you use Visual C++, you have two choices. You can use the -Gm option, together with one of the options -Zi or -ZI, discussed in Recipe 1.16. The -Gm option tells the compiler to build a database, stored in a file with the extension idb, containing information about dependencies between source files. The .idb file is created when a .cpp file, or collection of .cpp files, is initially compiled. On subsequent compilations, only those source files which have been modified or which depend on headers which have been modified are recompiled.
Alternatively, you can use the -showIncludes
option, together with the option -E. The -showIncludes option causes the compiler to output a message
to standard error each time an include
directive is
encountered. The -E option tells the compiler to run
the preprocessor and then exit, without building any binary files. Using a bit of shell
scripting, you can use the output generated by
-showIncludes to construct makefile
dependencies:
include john.d paul.d johnpaul.d %.d: %.cpp "$(MSVCDIR)/bin/cl" -E -showIncludes $< 2> $@.$$$$ > /dev/null; \ sed -n 's/^Note: including file: *\(.*\)/$*.obj·$*.d:\1/gp' \ < $@.$$$$ | sed 's:\\:/:g;s: :\\ :gp' > $@; \ rm -f $@.$$$$
In this example, the character · represents a Tab.
Let’s make one last improvement to Example
1-20. Currently, the sequence john
paul
johnpaul
occurs in two places; in the prerequisites of
the rule to build the static library, and in the include
directive used to generate dependencies. If the list of source files
changes, you’ll have to update the makefile in two locations. It’s better to define a
variable SOURCES
, and to replace both occurrences of
the sequence john
paul
johnpaul
with expressions involving SOURCES
:
SOURCES = john.cpp paul.cpp johnpaul.cpp ... # Build libjohnpaul.a from john.o, paul.o, and johnpaul.o $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) ar ru $@ $^ ranlib $@ ... # 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 $@.$$$$
Here I’m using the make function $(subst
x
,y
,str
)
, which replaces x
with
y
everywhere in str
.
Recipe 1.2 and Recipe 1.7
Get C++ Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.