The Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# "C" or "C++"
PROJECT_KIND := C
# "debug" or "release"
BUILD_KIND := debug

# change it as your want, default is "main"
EXEC := main

INC_DIR := include
SRC_DIR := src
BUILD_DIR := build
OBJ_DIR := $(BUILD_DIR)/obj
BIN_DIR	:= $(BUILD_DIR)/bin
DEP_DIR := $(BUILD_DIR)/dep
TARGET := $(BIN_DIR)/a.out

# compiler
CC := gcc
CXX := g++

# compiler flags
CFLAGS := -Wall -Wextra -std=c11
CXXFLAGS := -Wall -Wextra -Wconversion -Wshadow -std=c++17

# linker flags, like "-lm"
LDFLAGS :=

# header and source file extensions
C_HEADER_EXT := h
C_SOURCE_EXT := c
CXX_HEADER_EXT := hpp
CXX_SOURCE_EXT := cpp

HEADER_EXT :=
SOURCE_EXT :=
COMPILER :=
FLAGS :=

ifeq ($(PROJECT_KIND), C)
	HEADER_EXT = $(C_HEADER_EXT)
	SOURCE_EXT = $(C_SOURCE_EXT)
	COMPILER = $(CC)
	FLAGS = $(CFLAGS)
else
	HEADER_EXT = $(CXX_HEADER_EXT)
	SOURCE_EXT = $(CXX_SOURCE_EXT)
	COMPILER = $(CXX)
	FLAGS = $(CXXFLAGS)
endif

ifeq ($(BUILD_KIND), release)
	FLAGS += -DNDEBUG
endif

# ask compiler to generate dependency files
DEPFLAGS += -MMD -MP -MT $@ -MF $(DEP_DIR)/$*.d


INC := $(wildcard $(INC_DIR)/*.$(HEADER_EXT))
SRC := $(wildcard $(SRC_DIR)/*.$(SOURCE_EXT))
OBJ := $(patsubst $(SRC_DIR)/%.$(SOURCE_EXT),$(OBJ_DIR)/%.o,$(SRC))
DEP := $(patsubst $(SRC_DIR)/%.$(SOURCE_EXT),$(DEP_DIR)/%.d,$(SRC))


.PHONY: all, setup, run, clean

all: $(TARGET)
	@cp $(TARGET) $(EXEC)

$(TARGET): $(OBJ) | $(BIN_DIR)
	$(info ld    $@    $^)
	@$(COMPILER) $(LDFLAGS) -o $@ $^

$(OBJ): $(OBJ_DIR)/%.o: $(SRC_DIR)/%.$(SOURCE_EXT) $(DEP_DIR)/%.d | $(OBJ_DIR) $(DEP_DIR)
	$(info cc    $@    $<)
	@$(COMPILER) $(DEPFLAGS) $(FLAGS) -I$(INC_DIR) -c -o $@ $<


$(DEP):
include $(wildcard $(DEP))

$(DEP_DIR): ; [ -d $(DEP_DIR) ] || mkdir -p $(DEP_DIR)
$(OBJ_DIR): ; [ -d $(OBJ_DIR) ] || mkdir -p $(OBJ_DIR)
$(BIN_DIR): ; [ -d $(BIN_DIR) ] || mkdir -p $(BIN_DIR)

clean:
	@$(RM) $(EXEC) $(TARGET) $(OBJ) $(DEP)

The makefile is aimed at providing a fast, convenient snippet for building "simple" C or C++ projects.

You are supposed to organize your project tree as follows:

.
├── build
│   ├── bin
│   ├── dep
│   └── obj
├── include
├── src
└── makefile
  • Put headers in include directory.
  • Put source files in src directory. The main.{c,cpp} also stays in src.
  • All the compiled object files will be placed in build/obj directory.
  • The final single executable file goes into build/bin directory, you will also get a copy of that in the project root.
  • All the dependenciy files (with .d extension) generated by gcc stay in build/dep directory.

Usage:

  • make : Build the project, and necessary directories will be created.
  • make clean : Clean all the object, dependency and executable files.

I am NOT going to explain all these freaking shits in this makefile, but you could JUST USE IT

However, if you do have interests in those details, here are some keywords that might be helpful for you to search on the web:

  • gnu makefile
  • makefile rules
  • makefile targets
  • makefile variables
  • makefile prerequisites
  • makefile order-only prerequisites
  • makefile functions
  • makefile recipes
  • makefile phony targets
  • makefile automatic dependency generation
  • makefile automatic variables
  • makefile static pattern rules

Good luck and until next time!