Lab 2: Makefiles

Streamline Your Build Process

A Makefile is a powerful script used by the make build automation tool to manage and streamline the compilation of projects, especially in languages like C++. In this lab, you’ll learn the fundamentals of creating a Makefile to automate the build process for a simple C++ project.

§1 Basic Structure of a Makefile #

A Makefile is composed of a set of rules. Each rule follows this structure:

target: dependencies
    recipe

§2 Basic Example #

For this lab, imagine a simple C++ project with the following file structure:

/project
    main.cpp
    utils.cpp
    utils.h
    Makefile

Our goal is to compile main.cpp and utils.cpp into a single executable file named my_program.

Here is a well-structured Makefile for this project:

 1# Define the C++ compiler to use
 2CXX = g++
 3
 4# Define compiler flags for warnings, C++ standard, and optimization
 5CXXFLAGS = -Wall -Wextra -std=c++23 -O2
 6
 7# Define the name of the target executable
 8TARGET = my_program
 9
10# Automatically find all .cpp source files in the current directory
11SRCS = $(wildcard *.cpp)
12
13# Generate corresponding object file names by replacing .cpp with .o
14OBJS = $(SRCS:.cpp=.o)
15
16# The default rule, executed when you run `make` without arguments
17all: $(TARGET)
18
19# Rule to link the object files into the final executable
20$(TARGET): $(OBJS)
21	$(CXX) $(CXXFLAGS) -o $@ $^
22
23# Pattern rule to compile each .cpp source file into an object file
24%.o: %.cpp
25	$(CXX) $(CXXFLAGS) -c $< -o $@
26
27# Rule to clean up build artifacts
28clean:
29	rm -f $(OBJS) $(TARGET)
30
31# Declare targets that are not actual files
32.PHONY: all clean

§3 Explanation #

make intelligently builds your project by following the dependency chain. When you ask it to build all, it sees that all depends on $(TARGET). Then, it sees that $(TARGET) depends on $(OBJS). Finally, it uses the pattern rule to compile any outdated source files into object files before linking them all together.

§4 Running the Makefile #

From your project’s root directory (where the Makefile is located), you can run the following commands:

To compile the project:

make

To run the compiled program:

./my_program

To clean up the build files:

make clean

§5 Further Reading #

For a comprehensive guide on make and Makefiles, this tutorial is an excellent resource: https://makefiletutorial.com

§6 Questions #

  1. In a Makefile, what is the significance of using a tab character for indentation versus spaces?
  1. How would you modify the example Makefile to use the clang++ compiler instead of g++?
  1. In the rule %.o: %.cpp, what does the % symbol signify, and how does this rule help with building large projects containing many source files?
  1. How does make decide which targets need to be rebuilt?
  1. If you run make twice in a row without changing any source files, what happens during the second run and why?
  1. What is the purpose of the clean target, and why is it essential to declare it as .PHONY?
  1. If you run make, then modify only the utils.cpp file and run make again, which specific files will be recompiled and relinked, and why?
  1. In the example Makefile, is there any difference between running make and make all? Why or why not?

§7 Assignment #

Open the assignment link from the “Lab 2” Canvas page. Follow the directions in the README.md file.

Be sure to commit and sync your changes!

§7.1 Deliverables #

  1. A text submission or PDF containing answers to the Lab questions and a link to your lab2 code repository.