GNU Make¶
The GNU Make project provides a command-line tool and a configuration system to facilitate the building of complex projects and generate executables. It is also employed to automate command-line tasks. Read the the official documentation for a more detailed view.
make command¶
The command-line tool make
is how you run all tasks.
makefile¶
A makefile is the configuration file for the make command. It contains all the logics to guide the make to build the executables.
A makefile is usually named makefile
or Makefile
and locate in
top-level directory in a project. When you run the make
command, it will
search for makefile
or Makefile
in the current directory. You can use a
command-line argument -f <filename>
with the make
command to specify
the makefile of another name or in another directory.
The core component called rule in a makefile looked like this
target: prerequisites
recipe
- Target¶
It is usually the file you want to generate. However, virtual targets such as
main
,clean
are extensively used in many makefiles. They do not bind to a real file but can be used in the make command-line likemake main
ormake clean
- Prerequisites¶
It is a list of dependent files/targets needed to build the target. When any of the files or targets are updated, the target will be re-built.
- Recipe¶
An indented block including command-line commands in each line in order to generate the target.
Warning
The indentation for commands must be a single <tab> character for older versions of GNU make. The newer versions can take spaces for indentation.
If you do not know g++ command well, refer to the g++ document. One important thing to remember is never list header files in the g++ command-line! Provide cpp files only!
Modular Compilation¶
Let’s assume that we are writing a makefile for a project with multiple files.
The relationship among files are demonstrated in the diagram. Arrows means
include relationship: main.cpp -> student.hpp
means the student.hpp
file is included by the main.cpp
so the main.cpp
is depending on the
student.hpp
file.
For projects with multiple files, modular compilation is usually preferred. In this approach, .cpp source code files are compiled to .o object files first and then all object files are linked to provide the executable(s). It has many benefits over direct compilation to output executables from source code files:
Specify different g++ flags
Easier to debug
No need to recompile everything after a partial modification
Warning
Examples in this pages has all indentations as spaces. It is due to the limitation of the website generating system. You should change them to real
tab
characters to ensure the backward compatibility.
Hard-coded rules¶
A simple makefile with hard coded g++ commands:
# declare virtual targets
.PHONY: clean
# Linking to provide the executable
main: main.o grade-book.o student.o grades.o
g++ -o main main.o grade-book.o student.o grades.o
# Compile. One cpp file a time.
main.o: main.cpp grade-book.hpp student.hpp
g++ -std=c++14 -Wall -g -c main.cpp
student.o: student.cpp student.hpp
g++ -std=c++14 -Wall -g -c student.cpp
grades.o: grades.cpp grades.hpp
g++ -std=c++14 -Wall -g -c grades.cpp
grade-book.o: grade-book.cpp grade-book.hpp student.hpp grades.hpp
g++ -std=c++14 -Wall -g -c grade-book.cpp
# the 'clean' virtual target to remove temporary files
clean:
rm -rf *.o main *.gc* *.dSYM
The first rule is to produce the main
executable. It calls g++ to link a
list of object files to build the final program. The targets of .o files are
compilations rules to generate every object file. The clean target is used to
remove generated temporary files and the executable.
You may now build your executable main
by the command make
or make
main
. The make
command without parameters will take the first target in
the makefile as the target to make.
Introducing variables¶
You may noticed a lot of redundant texts such as the g++ flags (-std=c++14
-Wall -g
), list of file names, etc. Many of the redundancy can be remove by
introducing variables. With variables, you can also extract configurations out
of the commands for efficient management of configurations.
A makefile using variables:
CXX=g++
CXXFLAGS=-std=c++14 -Wall -g
RM=rm -rf
.PHONY: clean
main: main.o grade-book.o student.o grades.o
$(CXX) -o main main.o grade-book.o student.o grades.o
main.o: main.cpp grade-book.hpp student.hpp
$(CXX) $(CXXFLAGS) -c main.cpp
student.o: student.cpp student.hpp
$(CXX) $(CXXFLAGS) -c student.cpp
grades.o: grades.cpp grades.hpp
$(CXX) $(CXXFLAGS) -c grades.cpp
grade-book.o: grade-book.cpp grade-book.hpp student.hpp grades.hpp
$(CXX) $(CXXFLAGS) -c grade-book.cpp
clean:
$(RM) *.o main *.gc* *.dSYM
With variables, we can easily make modifications to the compiler command and compilation flags by modifying variables.
Introducing automatic variables¶
You may notice that we still have redundancy. The file names listed as part of the target or dependencies are hard-coded in the g++ commands. It can be improved using the automatic variables like $@, $^, $<.
The further simplified makefile:
CXX=g++
CXXFLAGS=-std=c++14 -Wall -g
RM=rm -rf
.PHONY: clean
main: main.o grade-book.o student.o grades.o
$(CXX) -o $@ $^
main.o: main.cpp grade-book.hpp student.hpp
$(CXX) $(CXXFLAGS) -c $<
student.o: student.cpp student.hpp
$(CXX) $(CXXFLAGS) -c $<
grades.o: grades.cpp grades.hpp
$(CXX) $(CXXFLAGS) -c $<
grade-book.o: grade-book.cpp grade-book.hpp student.hpp grades.hpp
$(CXX) $(CXXFLAGS) -c $<
clean:
$(RM) *.o main *.gc* *.dSYM
Note
The commands in the .o file rules used $< to avoid including header files in the g++ command-line. Make sure to put the .cpp file as the first dependency in the list!
Use Pattern Match¶
It is still verbose. By sacrificing the header prerequisites in the object file rules, we can merge these rules into one:
CXX=g++
CXXFLAGS=-std=c++14 -Wall -g
RM=rm -rf
.PHONY: clean
main: main.o grade-book.o student.o grades.o
$(CXX) -o $@ $^
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $<
clean:
$(RM) -rf *.o main *.gc* *.dSYM
Note
The drawback is that the .o file will not depend on certain header files and
the modification of the header files will not trigger the recompilation of
the .o file. A workaround is to force recompilation by running make clean
main
. This will be slow if your project is huge. No easy solution without
heavy coding. It is a limitation of make any.
Further Improvement¶
Now we have a very concise makefile.
You can learn potential improvement from the :
Store generated files in a subdirectory
Use wildcard to catch file names automatically
Pitfalls¶
# use the wrong variable
# the use of $^ with -c will include the hpp file in the g++ command
# use $< to only include the first prerequisite, which should be the cpp file
lib1.o: lib1.cpp lib1.hpp
g++ -o $@ $^
# the order of prerequisites are wrong
# it will cause the g++ to run with hpp file as its argument
# the cpp file should go first so $< will refer to it
lib1.o: lib1.hpp lib1.cpp
g++ -o $@ $<
# Typo in variables
main.o: main.cpp
$(CXX) $(CXXFLAG) -c $@ $<
Common Error Messages From make¶
Note
The terms ‘xxx’, ‘yyy’, etc. are placeholders of names. The term ‘#’ is a placeholder for numbers.
No rule to make target 'xxx'.
Wrong target name. For example, you try to run
make foo
butfoo
is not defined as a target name in themakefile
No rule to make target 'xxx.o', needed by 'yyy'.
This is a common error when make require a
xxx.cpp
file to compilexxx.o
but the file does not exist. Check for missing file or bad file name! Names likeXxx.cpp
is wrong because Linux is case sensitive with file names!missing separator. Stop.
It is likely that you have used the space characters for indentation. It is not supported in some old version of make. Use tab for indentation for best compatibility!
make: *** [makefile:#: xxx] Error 1
This is caused by a bad command runs by make.
Scroll up to see the last command that went wrong (mostly g++)
Also refer to the #th line in the makefile to see which command caused the problem. There are sometimes useful hints in the comments in the makefile.
warning: overriding recipe for target `xxx’
It means that you have multiple rules for a same target. It can be dangerous if you do not know which one is finally employed to build your target.
Debug makefile¶
You may use the make command with option -d to show debugging information. E.g. make -d main to show the details about target and rule matching process.