Writing Makefile for C++ Projects
Setting variables
Let’s start by setting some basic variables:
# The program name:
NAME = program
# The compiler
CXX = c++
Considerations
Note: The variable
CXX
is used to specify the C++ compiler. The commandc++
is analogous to usingcc
for compiling C programs. This is similar to howg++
/gcc
are used for GNU Compilers, andclang
/clang++
for Clang compilers.
Flags
Here, we are setting the compilation flags used in École 42’s C++ modules.
More specifically:
-Wall
: Enable all compiler’s warning messages-Wextra
: Enable extra warning messages that are not turned on by-Wall
-Werror
: Turn all warning messages into errors-std=c++98
: Specify the C++ version to conform
CXXFLAGS = -Wall -Wextra -Werror -std=c++98
Specifying the source files
In the SRCS
variable we can specify all the .cpp
files used in the project. These are the files that are going to be compiled into object files (.o
) and then combined into an executable file.
SRCS = main.cpp
OBJS = $(SRCS:.cpp=.o)
INCLUDES = main.hpp
Notes on OBJS
The
OBJS
variable is a special computed variable that takes all the values from theSRCS
variable that has the.cpp
extension and turns into a.o
file.
Defining some basic rules
When writing a Makefile we usually define 4 basic rules. Those are:
all
: Compile the programclean
: Clean all object filesfclean
: Clean all object files + the executable filere
: Clean everything that was generated and generate everything again (fclean
+all
)
all: $(NAME)
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) $(OBJS) -o $(NAME)
clean:
$(RM) $(OBJS)
fclean: clean
$(RM) $(NAME)
re: fclean all
Let’s analyze each line here.
all
To compile the program we just have to run make all
but as all
is the first rule that is getting specified, simply running make
will execute make all
.
all: $(NAME)
As we can see here, all
depends on the creation of $(NAME)
which is the program name, in other words the executable file. At this point, we don’t have the executable file yet, so make
searches for a rule that creates $(NAME)
:
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) $(OBJS) -o $(NAME)
The creation of $(NAME)
depends on the creation of all object files on this project so they are created. After the creation of $(OBJS)
we finally can run $(CXX)
(the compiler) specifying $(CXXFLAGS)
(our compilation flags) passing all the object files to compile and naming the final product to $(NAME)
.
To better illustrate this imagine you have 2 C++ files:
main.cpp
other.cpp
We want to name our executable file as program
:
c++ -Wall -Wextra -Werror -std=c++98 main.o other.o -o program
This is the actual command that gets executed.
Creating object files
This is the rule used to create object files. It basically says: For any C++ file, generate an object file. It also depends on the INCLUDES
so if any header file changes, all object files are going to get regenerated. Then it compiles each .cpp
file (represented by the automatic variable $<
here) to an object file (with the -c
flag) and outputs a file with the same name but with the .o
extension. Here $@
represents each .o
file:
%.o: %.cpp $(INCLUDES)
$(CXX) $(CXXFLAGS) -c $< -o $@
Implementing clean
To implement clean
, fclean
and re
is really simple.
clean
just removes all object files so:
clean:
$(RM) $(OBJS)
fclean
calls clean to remove all object files and in addition, also removes the executable file:
fclean: clean
$(RM) $(NAME)
re
runs fclean
and all
:
re: fclean all
.PHONY?
.PHONY
is used to indicate that a target is not a real file but a command or routine to be executed. For example, running make clean
executes the clean
routine, even though there is no file named clean
.
.PHONY: all clean fclean re
This sums up the creation of a basic Makefile for some basic C++ projects.