Generalizing our GNUmakefile#
Our first makefile required us to write a rule for every .cpp file
explicitly. Instead, we can use pattern rules
and wildcards
to automate a lot of the logic.
Here’s the improved GNUmakefile:
# by default, make will try to build the first target it encounters.
# here we make up a dummy name "ALL" (note: this is not a special make
# name, it is just commonly used).
ALL: planet_sort_split
# find all of the source files and header files
SOURCES := $(wildcard *.cpp)
HEADERS := $(wildcard *.H)
# create a list of object files by replacing .cpp with .o
OBJECTS := $(SOURCES:.cpp=.o)
# a recipe for making an object file from a .cpp file
# Note: this makes every header file a dependency of every object file,
# which is not ideal, but it is safe.
%.o : %.cpp ${HEADERS}
g++ -std=c++20 -c $<
# explicitly write the rule for linking together the executable
planet_sort_split: ${OBJECTS}
g++ -o $@ ${OBJECTS}
Let’s look at the different parts:
We use the wildcard function to match every source and every header in our directory:
SOURCES := $(wildcard *.cpp) HEADERS := $(wildcard *.H)
The
:=operator evaluates these expressions immediately whenmakeencounters them and stores the results in the variablesSOURCESandHEADERS.To reference the list of files in these variables, we use
${}, e.g.,${SOURCES}.We create the list of object files by automatically replacing the
.cppextension with a.oextension via themakeexpression:OBJECTS := $(SOURCES:.cpp=.o)
This is called a substitution reference.
We have a generic rule to compile a C++ file:
%.o : %.cpp ${HEADERS} g++ -std=c++20 -c $<
Note that we make every header file a dependency for every object file, whether or not it is included in the source file. This is not necessary, but simplifies how we specify the dependencies.
We use a make automatic variable,
$<to indicate the first name in the dependencies.We have a separate rule for the link step:
planet_sort_split: ${OBJECTS} g++ -o $@ ${OBJECTS}
We use another automatic variable here,
$@to indicate the name of the target, to be used as the executable name.Notice that all of the object files are dependencies for the final executable. This ensures that all of those are built first.
Using this GNUmakefile for other projects#
This version of our GNUmakefile is generic enough to work for any project
we do in this class. The main restriction is that all the source and
headers are in the same directory.
To use this with a different source, you just need to change planet_sort_split
to the basename of your source file that contains the main function.
This should be done both in the ALL target and in the final link rule
at the end.
Going further#
There are more things we can do to generalize our makefile.
We can use g++ to automatically determine dependencies. Also see the GNU make automatic prerequisites.
We can have the makefile create code at build time, for instance, writing a function that contains the current git hash of the code
We can different levels of build: optimized and debug
try it…
Modify the GNUmakefile to add the warning flags we discussed earlier to
the compilation of each source.
Tip
A common target in makefiles is clean, which will delete the intermediate
*.o files and the executable.