#=============================================================================
# 示例项目结构:
# my_project/
# ├── Makefile
# ├── src/
# │ ├── main.c
# │ └── module1.cpp
# └── include/
# ├── my_header.h
# └── module1.h
# └── lib/ (可选)
# └── my_library.a
#=============================================================================
#=============================================================================
# Makefile 模板: 适用于 C/C++ 项目的通用构建系统
# ----------------------------------------------------------------------------
# 用户需要修改的变量:
# PROJECT_NAME : 最终生成的可执行文件或库的名称
# SRC_DIRS : 包含源代码的目录列表 (相对路径, 空格分隔)
# INC_DIRS : 包含头文件的目录列表 (相对路径, 空格分隔)
# LIB_DIRS : 链接库的目录列表 (相对路径, 空格分隔)
# LIBS : 需要链接的库列表 (如 -lpthread -lrt 等, 空格分隔)
# CC : C 编译器 (默认为 gcc)
# CXX : C++ 编译器 (默认为 g++)
# CFLAGS : C 编译选项 (如 -Wall -Wextra -g)
# CXXFLAGS : C++ 编译选项 (如 -Wall -Wextra -g -std=c++17)
# LDFLAGS : 链接选项 (如 -static)
# BUILD_DIR : 编译中间文件 (.o, .d) 存放目录 (默认为 build)
#=============================================================================
# --- 用户可配置变量 ---
PROJECT_NAME := my_project
SRC_DIRS := src include
INC_DIRS := include
LIB_DIRS := lib
LIBS := -lm -lpthread
CC := gcc
CXX := g++
CFLAGS := -Wall -Wextra -O2 -MMD
CXXFLAGS := -Wall -Wextra -O2 -std=c++17 -MMD
LDFLAGS :=
BUILD_DIR := build
# --- 不建议用户修改的内部变量 ---
VPATH := $(SRC_DIRS) $(INC_DIRS)
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
LIB_FLAGS := $(addprefix -L,$(LIB_DIRS))
# 查找所有源文件 (根据扩展名自动识别 C 或 C++ 源文件)
C_SRCS := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.c))
CPP_SRCS := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp) $(wildcard $(dir)/*.cc) $(wildcard $(dir)/*.cxx))
SRCS := $(C_SRCS) $(CPP_SRCS)
# 生成对应的目标文件 (.o) 和依赖文件 (.d) 路径
# 将源文件路径中的目录部分去除,然后加上 BUILD_DIR 前缀和 .o/.d 后缀
OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(C_SRCS))) \
$(patsubst %.cpp,$(BUILD_DIR)/%.o,$(notdir $(CPP_SRCS))) \
$(patsubst %.cc,$(BUILD_DIR)/%.o,$(notdir $(CPP_SRCS))) \
$(patsubst %.cxx,$(BUILD_DIR)/%.o,$(notdir $(CPP_SRCS)))
DEPS := $(OBJS:.o=.d)
# 默认目标
.PHONY: all clean rebuild
all: $(BUILD_DIR) $(PROJECT_NAME)
# 创建构建目录
$(BUILD_DIR):
@mkdir -p $(BUILD_DIR)
# 链接可执行文件
$(PROJECT_NAME): $(OBJS)
@echo "Linking $(PROJECT_NAME)..."
$(CXX) $(OBJS) $(LIB_FLAGS) $(LIBS) $(LDFLAGS) -o $@
# C 源文件编译规则
$(BUILD_DIR)/%.o: %.c
@echo "Compiling C: $<"
$(CC) $(CFLAGS) $(INC_FLAGS) -c $< -o $@ -MF $(@:.o=.d)
# C++ 源文件编译规则
$(BUILD_DIR)/%.o: %.cpp
@echo "Compiling C++: $<"
$(CXX) $(CXXFLAGS) $(INC_FLAGS) -c $< -o $@ -MF $(@:.o=.d)
$(BUILD_DIR)/%.o: %.cc
@echo "Compiling C++: $<"
$(CXX) $(CXXFLAGS) $(INC_FLAGS) -c $< -o $@ -MF $(@:.o=.d)
$(BUILD_DIR)/%.o: %.cxx
@echo "Compiling C++: $<"
$(CXX) $(CXXFLAGS) $(INC_FLAGS) -c $< -o $@ -MF $(@:.o=.d)
# 清理目标文件和可执行文件
clean:
@echo "Cleaning build directory and project executable..."
$(RM) -r $(BUILD_DIR) $(PROJECT_NAME)
# 重新构建
rebuild: clean all
# 包含自动生成的依赖文件 (.d 文件)
# `-include` 表示如果文件不存在也不会报错,这在第一次编译时很有用
-include $(DEPS)
保存为 Makefile
: 将上述代码保存为名为 Makefile 的文件,放在你的项目根目录下。
PROJECT_NAME
: 修改为你的可执行文件(或库)的名称。
SRC_DIRS
: 列出所有包含 .c, .cpp, .cc, .cxx
源文件的目录。例如,如果你的源文件在 src/
和 test/
目录下,那么 SRC_DIRS := src test
。
INC_DIRS
: 列出所有包含 .h
头文件的目录。这些目录会通过 -I 选项添加到编译器的头文件搜索路径中。
LIB_DIRS
: 如果你的项目需要链接非标准路径下的库,请在这里添加库文件所在的目录。这些目录会通过 -L 选项添加到链接器的库搜索路径中。
LIBS
: 添加你需要链接的库名称,例如 LIBS := -lm -lpthread
。
CC, CXX
: 如果你需要使用特定的 C/C++ 编译器(例如 clang, arm-none-eabi-gcc),在这里修改。
CFLAGS, CXXFLAGS
: 添加 C 和 C++ 的编译选项。常用的有 -Wall -Wextra (开启所有警告), -O2 (优化级别), -g (生成调试信息), -std=c++17 (C++ 标准)
。注意:务必保留 -MMD,它用于自动生成 .d 依赖文件。
LDFLAGS
: 添加链接选项,例如 -static。
BUILD_DIR
: 编译中间文件 (.o, .d) 的存放目录。默认是 build/。
编译项目: 在终端中进入项目根目录,然后简单地运行 make 或 make all。
清理中间文件: 运行 make clean 会删除 BUILD_DIR 目录和最终的可执行文件。
重新编译: 运行 make rebuild 会先清理再重新编译整个项目。
VPATH:这是一个 make 特性,告诉 make 在哪里寻找源文件。当目标文件依赖于一个没有明确路径的源文件时,make 会在 VPATH 中指定的目录里搜索。
INC_FLAGS 和 LIB_FLAGS:这两个变量将 INC_DIRS 和 LIB_DIRS 中的路径转换为编译器和链接器所需的 -I 和 -L 选项格式。
wildcard 和 foreach:用于在指定目录中查找所有符合特定模式的源文件。
patsubst 和 notdir:这些 make 函数用于处理文件名。notdir 提取文件名(不带路径),patsubst 用于替换字符串,这里是将 .c/.cpp 后缀替换为 $(BUILD_DIR)/%.o,从而生成中间目录下的目标文件路径。
.PHONY:声明 all, clean, rebuild 为伪目标。这意味着它们不是实际的文件,make 不会尝试去查找同名文件来判断是否需要执行。
-MMD 编译选项:这是 gcc/g++ 的一个重要选项。它告诉编译器在编译源文件时,同时生成一个 .d 文件,其中包含了该源文件所依赖的所有头文件。
-MF ( @ : . o = . d ) : − M F 指定生成的依赖文件的名称。 (@:.o=.d):-MF 指定生成的依赖文件的名称。 (@:.o=.d):−MF指定生成的依赖文件的名称。(@:.o=.d) 是一个 make 字符串替换,例如,如果目标文件是 build/main.o,那么依赖文件就是 build/main.d。一个 .d 文件的内容可能看起来像这样:main.o: main.c my_header.h common.h /usr/include/stdio.h
-include $(DEPS):这是关键!它告诉 make 包含所有自动生成的 .d 文件。这样,当任何一个头文件被修改时,make 就能知道哪些源文件需要重新编译。_ 前缀的 - 表示即使 .d 文件在第一次编译时不存在,make 也不会报错。