happy ethnic woman sitting at table with laptopPhoto by Andrea Piacquadio on <a href="https://www.pexels.com/photo/happy-ethnic-woman-sitting-at-table-with-laptop-3769021/" rel="nofollow">Pexels.com</a>

To spare yourself the headaches related to the building and linking processes of compilation, I’d like to introduce the concept of a Makefile for C++. These files serve as a ruleset to execute complex compilation tasks and linking with dependencies.

Table of contents

What is a Makefile?

A Makefile is a document to automate the software building process and to execute complex tasks and sometimes also linking a program with dependencies.

Furthermore, these files may contain dependency rules, environment discovery tasks and implicit rules, e.g. make install. Most open source projects use make to compile a final executable binary which is then installed using make install. In fact, you may have used a Makefile for C++ before, in case you ever compiled a program from source code using Linux distributions or MacOS.

Makefiles are used to determine which parts of a larger software need to be recompiled. In the vast majority of use cases, a Makefile for C++ is used to execute the compilation process. But make can also be used beyond compilation, e.g. when you want to have a series of instructions run depending on some files that changed.

In this free source code, we focus on the Makefile for C++ use case.

Defining variables in a Makefile

First, let’s have a look at some basic variables definition inside a Makefile. In this example, the variable just hold simple values, that we will later print in your terminal.

TARGET_NAME = "MyAwesomeApp"
TARGETS_DIR = .
SOURCES_DIR = ./src
OBJECTS_DIR = ./build

# (1) Defines a target file path
TARGET = $(TARGETS_DIR)/$(TARGET_NAME)

(1) In this first step you define a target executable file path. The target represents the full name of the file that is produced after compilation. In this case, you are using MyAwesomeApp as a name and the dot (.) as a folder, which means that the variable TARGET contains ./MyAwesomeApp.

Next we’ll define variables that you may want to change depending on the operating system that you are using. These variables contain program commands, i.e. when they are used they will operate the execution of a program.

This type of variable is what we will use in the Makefile for C++, to determine the compiler software and the C++ standard library version (C++11, C++17, etc.).

CC = g++ -arch x86_64
XX_FLAGS = -std=c++11

# (2) Defines a custom command
CXX = $(CC) $(XX_FLAGS)

(2) In this next step you define a command that will be used to compile C++ files into object files. In a manual compilation effort, you would usually execute g++ -std=c++11 -c src/main.cpp -o build/main.o for every file in your program source code. You can see where the above command makes sense.

Running commands in a Makefile

Now, we’ll have a look at some more thorough variables and start writing rules in your Makefile for C++ as well. So let’s define two important variables SOURCES and OBJECTS, which respectively represent the program’s source code files (.cpp) and the built compilation object files (.o).

# (3) Execute a shell command: find
SOURCES = $(shell find $(SOURCES_DIR) -type f -name *.cpp)

# (4) Path substitution + file name extension change
OBJECTS := $(patsubst $(SOURCES_DIR)/%,$(OBJECTS_DIR)/%,$(SOURCES:.cpp=.o))

(3) We first define the SOURCES variable which shall contain the file names of all the .cpp files in our program. These files can be found in the src/ and the SOURCES_DIR variable helps us with that.

(4) Next we define a more complicated variable in that it needs to do a path substitution (patsubst) to change the SOURCES_DIR path to OBJECTS_DIR and the file extension from .cpp to .o.

This last variable is used to create compilation tasks dynamically in your Makefile, as you will see below. But first let’s create a simple make clean rule that removes all the mess that the compilation process creates.

# (5) Cleaning your mess...
clean:
	rm -f $(OBJECTS_DIR)/* $(TARGET)

(5) This looks quite simple, doesn’t it? We use the rm command to delete compilation object files and the target executable file. And we named this rule clean, such that you can execute it using make clean in your terminal.

Now let’s create a compilation rule that takes in source code files and generates object files thereof. These object files with later be linked with the program.

# (6) Compilation rules
$(BUILD_DIR)/%.o: $(SOURCES_DIR)/%.cpp
	@echo "Compiling $<..."; $(CXX) -c -o $@ $<

(6) In this step we define a compilation rule, that is dependent of all source code files, a more manual display of the above could be the following example:

# (7) Writing it the manual way (not recommended)
build/main.o: src/main.cpp
	@echo "Compiling src/main.cpp..."; $(CXX) -c -o build/main.o src/main.cpp

And so on,… for every source code file of your program. I think you do understand why the rule in (6) is more practical, and the one here above can be used to understand some special feats of Makefiles like $< and $@, which respectively represent the rule dependency name, e.g. src/main.cpp, and the rule name, e.g. build/main.o.

You can also execute this compilation rule from (7) simply by running make build/main.o which would produce the build/main.o object file via compiling the src/main.cpp source code file.

Note that this step is also where include files must be configured correctly.

Linking a program with a Makefile

Finally, we’ll have to create an executable file – or a library file – using the compiler software’s linker. It is called a linker because it uses the compiled object files and links them to your program. Note that this step is also where external libraries must be configured correctly.

# (8) Put this rule after variables (first rule)
${TARGET}: clean $(OBJECTS)
	@echo "Linking $(TARGET)"; $(CC) $(OBJECTS) -o $(TARGET)
	# (9) Make the file executable
	@chmod a+x $(TARGET)
	@echo ""
	@echo "$(TARGET_NAME) built successfully"

(8) Now we define a dynamic rule that is named after the TARGET, which means ./MyAwesomeApp as we defined earlier. What the above rule will permit is two things, one is that you can run: make ./MyAwesomeApp to build your app, but putting is as the first rule in your Makefile also permits to just execute make, leaving the rule name out.

This rule is dependent of the clean rule and all the object files of the program. This effectively means that the process will first clean all compilation files, then compile all source code files into object files, and then execute the linking process that creates the MyAwesomeApp target file.

We use the at-sign (@) in front of commands to keep their execution hidden from the display in your terminal. In fact everything that the terminal will display is: Linking ./MyAwesomeApp. Then, in (9), we use the chmod command to make the resulting file executable using the +x flag.

Complete example of a Makefile for C++

Now, let’s bring all the above together in one Makefile that can be used to build most of your software written with C++. This file will contain the same rules as we have just explained and functions the same.

# Makefile
TARGET_NAME = MyProgram
TARGET_FILE = "$(TARGET_NAME)"
TARGETS_DIR = .
OBJECTS_DIR = ./build
SOURCES_DIR = ./src

TARGET = $(TARGETS_DIR)/$(TARGET_FILE)

# For Linux and Windows use "g++"
CC = clang++ -arch x86_64
XX_FLAGS = -std=c++11
CXX = $(CC) $(XX_FLAGS)

# Dynamic source files finder
SOURCES = $(shell find $(SOURCES_DIR) -type f -name *.cpp)
OBJECTS := $(patsubst $(SOURCES_DIR)/%,$(OBJECTS_DIR)/%,$(SOURCES:.cpp=.o))

# Operation run with "make"
${TARGET}: clean $(OBJECTS)
	@echo "Linking $(TARGET)"; $(CC) $(OBJECTS) -o $(TARGET) -lcryptopp
	@chmod a+x $(TARGET)
	@echo ""
	@echo "$(TARGET_NAME) built successfully"
	@echo "To run $(TARGET_NAME), use: $(TARGET)"

# Singular object compilation
$(OBJECTS_DIR)/%.o: $(SOURCES_DIR)/%.cpp
	@echo "Compiling $<..."; $(CXX) -c -o $@ $<

# Cleaning your mess...
clean:
	rm -f $(OBJECTS_DIR)/* $(TARGET)

Conclusion

Using the make build system is surely a big help when your software is built with C++ and when it is a program that contains a lot of source code split amongst separate modules or headers and libraries.

I hope this article was helpful in your endeavours developing software with C++ and make sure to save this page!

Editorial Team

By Editorial Team

A source code library for the open world! Find snippets and examples for popular programming languages including PHP, Javascript, Typescript, Node.js, Vue.js, and more.