Makefile
Install make
- Unix/Linux/macOS:
makeis included in the default environment. - Windows:
- Install scoop
- Install
gitbyscoop install git. - Add bucket
mainbyscoop bucket add main. - Install
makebyscoop install make
Why Makefile?
- You want to avoid hard-coding paths
- You need to build a package on more than one computer
- You want to support multiple compilers
- You want to describe how your program is structured logically, not flags and commands
- …
Compile a program
Now you already know how to build a program with the following structure:
├── SimpleMC.cpp
├── SimpleMC.h
├── SimpleMCmain2.cpp
├── payoff
│ ├── PayOff1.cpp
│ └── PayOff1.h
└── random
├── Random1.cpp
└── Random1.hWhat if we changed the PayOff1.h file? Then the tasks are tedious:
- We have to re-compile Payoff1.cpp.
- We have to re-compile SimpleMC.cpp.
- We have to re-compile SimpleMCmain2.cpp.
Makefile syntax
A Makefile is a text file named Makefile or makefile.
targets: dependencies
commands
commands
commands- The
targetsis the target file that we want to generate. - The
dependenciesare the files that the target file depends on. - The
commandsare the commands which generate the target file from the dependencies.
Before the commands, there must be a tab character.
A simple Makefile
You can actually use Makefile to generate any file.
hello.txt:
echo "Hello, World!" > hello.txthello.txtis the target file.- No dependencies.
echo "Hello, World!" > hello.txtis the command to generate the target file.
Run the Makefile
To run the Makefile, you can type make in the terminal.
makewill generate the first target file.You can also specify the target file to generate.
make hello.txt
In the example, as long as the target file exists, make will not generate it again.
- Because there’s no dependencies for
hello.txt.
Another example
hello2.txt: hello1.txt
cp hello1.txt hello2.txt
hello1.txt:
echo "Hello, World!" > hello1.txt- If I just type
make, the first target file is the default target to be built.- If there’s no
hello1.txt, it will generatehello1.txtfirst, then generatehello2.txtfromhello1.txt. - If there’s already
hello1.txtand nohello2.txt, it will generatehello2.txtfromhello1.txt. - If there’s already both
hello1.txtandhello2.txt, it will generatehello2.txtbased on ifhello1.txtis modified.
- If there’s no
Use Makefile to build a program
Unix:
hello: main.cpp
g++ -o hello main.cpp
echo "Build hello"Windows:
hello: main.cpp
cl main.cpp /EHsc /Fehello
echo "Build hello"When you run make,
- The first target file is
hello, it will be the default target to be built. hellodepends onmain.cpp.- Make will decide if run the commands if:
- No
helloexecutable file exists. - The
main.cppis modified. (It’s newer thanhelloexecutable file.)
- No
Variables in Makefile
Common Variables:
SRC_DIR = ./src
BUILD_DIR = ./build
OBJS = $(BUILD_DIR)/*.o
CC = gcc
CXX = g++
CFLAGS = -std=c++17 -Wall -Werror -Wextra -Wsign-conversionSRC_DIR = ./src
BUILD_DIR = ./build
OBJS = $(BUILD_DIR)/*.o
CC = cl
CXX = cl
CFLAGS = /EHsc /W4 /std:c++17CCoften means the compiler for C language.CXXoften means the compiler for C++ language.CFLAGSare the flags for the compiler.- Use
$(variable_name)to get the value of a variable.
Use Makefile to build a program
Without using variables:
hello: hello.o main.o
g++ -Wall -std=c++11 -o hello hello.o main.o
hello.o: hello.cpp hello.h
g++ -Wall -std=c++11 -c hello.cpp
main.o: main.cpp hello.h
g++ -Wall -std=c++11 -c main.cpp
.PHONY: clean
clean:
rm hello hello.o main.oUsing variables:
CXX := g++
CXXFLAGS := -Wall -std=c++11
objects := hello.o main.o
hello: $(objects)
$(CXX) $(CXXFLAGS) -o hello $(objects)
hello.o: hello.cpp hello.h
$(CXX) $(CXXFLAGS) -c hello.cpp
main.o: main.cpp hello.h
$(CXX) $(CXXFLAGS) -c main.cpp
.PHONY: clean
clean:
rm hello $(objects)Phony targets
Phony targets are targets that are not actually files.
The most common phony target is clean.
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)- If there’s a file named
cleanin the current directory, it will be ignored. - Because
cleanis a phony target,make cleanwill always run the commands regardless if there’s a file namedcleanin the current directory.
A more complex Makefile
Now let’s see how to compile what we did in the last lab.
# Compiler and flags
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Werror -Wextra -Wsign-conversion
# Directories
SRC_DIR = ./vanilla_payoff_class
BUILD_DIR = ./build
# Include paths
INCLUDES = -I$(SRC_DIR) -I$(SRC_DIR)/payoff -I$(SRC_DIR)/random
# Object files
OBJ = $(BUILD_DIR)/SimpleMC.o \
$(BUILD_DIR)/SimpleMCmain2.o \
$(BUILD_DIR)/PayOff1.o \
$(BUILD_DIR)/Random1.o
$(BUILD_DIR)/SimpleMCmain2: $(OBJ)
@mkdir -p $(@D) # $(@D) means the directory of the target file
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $(BUILD_DIR)/SimpleMCmain2 $(OBJ)
$(BUILD_DIR)/PayOff1.o: $(SRC_DIR)/payoff/PayOff1.cpp $(SRC_DIR)/payoff/PayOff1.h
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $(SRC_DIR)/payoff/PayOff1.cpp -o $(BUILD_DIR)/PayOff1.o
$(BUILD_DIR)/Random1.o: $(SRC_DIR)/random/Random1.cpp $(SRC_DIR)/random/Random1.h
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $(SRC_DIR)/random/Random1.cpp -o $(BUILD_DIR)/Random1.o
$(BUILD_DIR)/SimpleMC.o: $(SRC_DIR)/SimpleMC.cpp $(SRC_DIR)/SimpleMC.h
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $(SRC_DIR)/SimpleMC.cpp -o $(BUILD_DIR)/SimpleMC.o
$(BUILD_DIR)/SimpleMCmain2.o: $(SRC_DIR)/SimpleMCmain2.cpp $(SRC_DIR)/SimpleMC.h
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $(SRC_DIR)/SimpleMCmain2.cpp -o $(BUILD_DIR)/SimpleMCmain2.o
clean:
rm -rf $(BUILD_DIR)
.PHONY: cleanOptional: Automatic variables and wildcards
$@is the target file.$<is the first dependency.$^is all the dependencies.*: Matches zero or more characters in a filename or string.- For example,
*.cppmatches all the.cppfiles in the current directory. - Like saying “everything that ends with
.cpp”.
- For example,
%: Matches zero or more characters in a filename or string.- For example,
%.omatches each.ofile in the current directory. - Like saying “anything that ends with
.o” individually.
- For example,
Using automatic variables, the Makefile can be simplified to:
# Compiler and flags
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Werror -Wextra -Wsign-conversion
# Directories
SRC_DIR = src
BUILD_DIR = build
# Include paths
INCLUDES = -I$(SRC_DIR) -I$(SRC_DIR)/payoff -I$(SRC_DIR)/random
# Source files
SRC = $(SRC_DIR)/SimpleMC.cpp \
$(SRC_DIR)/SimpleMCmain2.cpp \
$(SRC_DIR)/payoff/PayOff1.cpp \
$(SRC_DIR)/random/Random1.cpp
# Object files
OBJ = $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRC))
# Target executable
TARGET = $(BUILD_DIR)/SimpleMCmain2
# Default target
all: $(TARGET)
# Rule for making the target
$(TARGET): $(OBJ)
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) -o $@ $^
# Rule for object files
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
# Clean rule
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)No need to understand it, just take a look.
Assignment
Star Pattern:
Create a cpp file named star_pattern.cpp that print diffeent star patterns based on the input size n.
- It will ask user to input the size
nbetween 1 and 9. - Based on the input size
n, the program will print different star patterns.
The star patterns are shown in the next page, you must use loops/nested loops to print them.
For example, if n is 5, the program will print:
Right Triangle:
*
**
***
****
*****
Left Triangle:
*
**
***
****
*****Bonus(2pts): Add a pattern that print a Diamond:
Diamond:
*
***
*****
*******
*********
*******
*****
***
*Assignment 2
Use makefile to build the following program named main:
.
├── add.cpp
├── add.h
├── main.cpp
├── mul.cpp
└── mul.h- Please make sure that all the object files are put under current directory or build directory(Either is fine).
- Remember to define a phony target
clean. - As long as makefile can be correctly run, you will get full credits.
- No need to change the file structure.
- You are free to use any variables, wildcards, and automatic variables or not.
Submission
- Submit star_pattern.cpp for assignment 1.
- Zip the whole folder(including all the files and makefile) and submit it in Canvas.