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 like make main or make 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.

digraph dep {
    "main.cpp" -> {"grade-book.hpp", "student.hpp"};
    "student.cpp" -> "student.hpp";
    "grade-book.cpp" -> {"grade-book.hpp", "student.hpp", "grades.hpp"};
    "grades.cpp" -> "grades.hpp";
}

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 target

$^

all prerequisites

$<

the first prerequisite

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 but foo is not defined as a target name in the makefile

  • No rule to make target 'xxx.o', needed by 'yyy'.

    This is a common error when make require a xxx.cpp file to compile xxx.o but the file does not exist. Check for missing file or bad file name! Names like Xxx.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.