Makefile

Install make

  • Unix/Linux/macOS: make is included in the default environment.
  • Windows:
    • Install scoop
    • Install git by scoop install git.
    • Add bucket main by scoop bucket add main.
    • Install make by scoop 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.h

What 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 targets is the target file that we want to generate.
  • The dependencies are the files that the target file depends on.
  • The commands are the commands which generate the target file from the dependencies.
Note

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.txt
  • hello.txt is the target file.
  • No dependencies.
  • echo "Hello, World!" > hello.txt is the command to generate the target file.

Run the Makefile

To run the Makefile, you can type make in the terminal.

  • make will 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.
    1. If there’s no hello1.txt, it will generate hello1.txt first, then generate hello2.txt from hello1.txt.
    2. If there’s already hello1.txt and no hello2.txt, it will generate hello2.txt from hello1.txt.
    3. If there’s already both hello1.txt and hello2.txt, it will generate hello2.txt based on if hello1.txt is modified.

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.
  • hello depends on main.cpp.
  • Make will decide if run the commands if:
    • No hello executable file exists.
    • The main.cpp is modified. (It’s newer than hello executable file.)

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-conversion
SRC_DIR = ./src
BUILD_DIR = ./build

OBJS = $(BUILD_DIR)/*.o

CC = cl
CXX = cl
CFLAGS = /EHsc /W4 /std:c++17
  • CC often means the compiler for C language.
  • CXX often means the compiler for C++ language.
  • CFLAGS are 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.o

Using 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 clean in the current directory, it will be ignored.
  • Because clean is a phony target, make clean will always run the commands regardless if there’s a file named clean in 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: clean

Optional: 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, *.cpp matches all the .cpp files in the current directory.
    • Like saying “everything that ends with .cpp”.
  • %: Matches zero or more characters in a filename or string.

    • For example, %.o matches each .o file in the current directory.
    • Like saying “anything that ends with .o” individually.

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 n between 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.