Make Makefile CMake 入门笔记

📅 2026/7/19 7:29:03
Make  Makefile  CMake 入门笔记
一、为什么需要构建工具手动编译的痛点单文件 C 程序手动编译很简单gcc main.c -o main但真实项目通常长这样project/ ├── main.c # 主程序 ├── utils.c # 工具函数实现 ├── utils.h # 工具函数声明 ├── network.c # 网络模块 └── network.h手动编译要写一长串gcc main.c utils.c network.c -o app问题来了文件多了命令越来越长改一个文件就要全量重新编译慢跨平台Windows/Linux/Mac编译参数不一样依赖第三方库时参数复杂构建工具的作用把“怎么编译”写成规则文件让工具自动判断哪些需要重新编译只编译改动过的部分。二、Make 简介Make 是什么GNU Make 是一个构建自动化工具通过读取Makefile文件中的规则来决定如何编译只重新编译发生改动的文件提升效率几乎所有 Unix/Linux 系统自带安装平台方式Linuxsudo apt install makeDebian/UbuntuMacxcode-select --install或brew install makeWindows安装 MinGW 后自带或用 MSYS2验证安装make --version三、Makefile 基础语法3.1 规则的基本结构Makefile 的核心是“规则”目标(target): 依赖(prerequisites) 命令(recipe)命令行前必须用 Tab 缩进不能用空格3.2 第一个 Makefile目录结构hello/ ├── main.c └── Makefilemain.c#include stdio.h int main() { printf(Hello, Make!\n); return 0; }Makefilemain: main.c gcc main.c -o main执行make # 构建第一个目标 ./main # 运行3.3 多文件项目目录结构project/ ├── main.c ├── utils.c ├── utils.h └── Makefilemain.c#include stdio.h #include utils.h int main() { printf(5 3 %d\n, add(5, 3)); return 0; }utils.hint add(int a, int b);utils.c#include utils.h int add(int a, int b) { return a b; }Makefile最朴素的写法main: main.o utils.o gcc main.o utils.o -o main main.o: main.c utils.h gcc -c main.c -o main.o utils.o: utils.c utils.h gcc -c utils.c -o utils.o 清理目标 clean: rm -f main main.o utils.o执行流程make # 只编译改动过的文件 make clean # 清理 make # 重新全量构建Make 的工作机制找到第一个目标main检查依赖main.o utils.o是否存在/是否比目标新若依赖需要更新递归执行对应规则依赖都就绪后执行main的命令四、Makefile 变量4.1 自定义变量CC gcc CFLAGS -Wall -g TARGET main SRCS main.c utils.c $(TARGET): $(SRCS) $(CC) $(CFLAGS) $(SRCS) -o $(TARGET)变量名习惯用大写引用变量$(变量名)等号两边空格无所谓4.2 变量赋值方式写法名称说明递归展开使用时才展开可能死循环:直接展开定义时就展开推荐?条件赋值没定义过才赋值追加在原值后追加CC : gcc CFLAGS ? -Wall # 只在 CFLAGS 未定义时赋值 CFLAGS -g # 追加 -g结果为 -Wall -g4.3 自动变量重点Make 内置的快捷变量在命令中使用变量含义$当前目标的名字$第一个依赖的名字$^所有依赖的名字去重$?比目标新的依赖列表$*目标模式中%匹配的部分改造上面的 MakefileCC gcc CFLAGS -Wall -g main: main.o utils.o $(CC) $^ -o $ # $^ main.o utils.o, $ main main.o: main.c utils.h $(CC) $(CFLAGS) -c $ -o $ # $ main.c, $ main.o utils.o: utils.c utils.h $(CC) $(CFLAGS) -c $ -o $ # $ utils.c, $ utils.o clean: rm -f main *.o五、伪目标 .PHONY问题场景如果目录里恰好有个文件叫cleantouch clean make clean # Make 会认为 clean 已经是最新的不执行解决方案用.PHONY声明为伪目标.PHONY: clean clean: rm -f main *.o.PHONY告诉 Make这个目标不是文件每次都执行命令。常见伪目标all、clean、install、test、run。.PHONY: all clean run all: main run: main ./main clean: rm -f main *.o六、模式规则通配规则6.1 用%简化重复规则前面的 Makefile 里main.o和utils.o的规则几乎一样可以用模式规则合并%.o: %.c $(CC) $(CFLAGS) -c $ -o $含义所有.o文件都由同名.c文件编译而来。6.2 完整改进版CC gcc CFLAGS -Wall -g TARGET main SRCS main.c utils.c OBJS $(SRCS:.c.o) # 把 .c 替换成 .o得到 main.o utils.o $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)$(SRCS:.c.o)是变量替换引用把SRCS里每个.c后缀替换成.o。七、常用内置函数7.1 wildcard — 获取匹配文件SRCS $(wildcard *.c) # 列出当前目录所有 .c 文件7.2 patsubst — 模式替换OBJS $(patsubst %.c, %.o, $(SRCS)) # 把 .c 列表替换成 .o 列表7.3 实战全自动 MakefileCC gcc CFLAGS -Wall -g TARGET main 自动收集所有 .c 文件 SRCS $(wildcard *.c) 自动生成对应的 .o 文件列表 OBJS $(patsubst %.c, %.o, $(SRCS)) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)新增.c文件无需改 Makefile自动识别。八、CMake 简介Makefile 的局限跨平台麻烦Windows 的 nmake 和 GNU make 语法不同大型项目手写 Makefile 复杂依赖管理弱CMake 是什么跨平台的构建系统生成器不直接编译而是根据CMakeLists.txt生成对应平台的构建文件Linux/Mac → MakefileWindows → Visual Studio 工程或 Ninja几乎所有开源 C/C 项目都用 CMake工作流程CMakeLists.txt → cmake → Makefile → make → 可执行文件安装官网https://cmake.org/download/Linuxsudo apt install cmakeMacbrew install cmakeWindows下载安装包验证cmake --version九、CMakeLists.txt 基本写法9.1 最简示例目录结构project/ ├── CMakeLists.txt └── main.cCMakeLists.txt# 要求的 CMake 最低版本 cmake_minimum_required(VERSION 3.10) 项目名称和语言 project(HelloMake) 生成可执行文件 main源码是 main.c add_executable(main main.c)构建mkdir build # 外部构建避免污染源码目录 cd build cmake .. # 生成 Makefile make # 编译 ./main # 运行外部构建是好习惯所有生成文件都在build/里删掉即清空。9.2 多文件项目cmake_minimum_required(VERSION 3.10) project(MyApp C) set(CMAKE_C_STANDARD 99) # C99 标准 set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -Wall -g) add_executable(main main.c utils.c network.c)9.3 用变量管理源文件cmake_minimum_required(VERSION 3.10) project(MyApp) 用变量收集源文件 set(SRCS main.c utils.c network.c ) add_executable(main ${SRCS})9.4 自动收集源文件cmake_minimum_required(VERSION 3.10) project(MyApp) 收集 src 目录下所有 .c 文件 file(GLOB SRCS src/*.c) add_executable(main ${SRCS})十、CMake 常用指令10.1 包含头文件目录include_directories(include) # 添加 include/ 为头文件搜索路径10.2 链接库文件# 链接系统库 target_link_libraries(main pthread) # 链接 pthread 链接第三方库目录 link_directories(/usr/local/lib) target_link_libraries(main mylib)10.3 设置输出目录set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)10.4 添加子目录多模块项目项目结构project/ ├── CMakeLists.txt ├── main.c ├── utils/ │ ├── CMakeLists.txt │ ├── utils.c │ └── utils.h └── network/ ├── CMakeLists.txt ├── network.c └── network.h根目录CMakeLists.txtcmake_minimum_required(VERSION 3.10) project(MyProject) 添加子目录 add_subdirectory(utils) add_subdirectory(network) 主程序 add_executable(main main.c) 链接子目录生成的库 target_link_libraries(main utils network)utils/CMakeLists.txt# 创建静态库 add_library(utils STATIC utils.c)network/CMakeLists.txt# 创建静态库 add_library(network STATIC network.c)这样每个模块独立管理根目录只需add_subdirectory即可。十一、总结与对比Make vs CMake方面MakeCMake定位构建工具构建系统生成器配置文件MakefileCMakeLists.txt跨平台弱需写不同版本强自动生成对应构建文件语法复杂度较低但规则繁琐较高但更结构化依赖管理弱需手动处理强支持 find_package适用场景小型项目、脚本自动化中大型项目、跨平台项目如何选择选 Make项目简单、只需在 Linux/Mac 上编译、想深入理解编译过程。选 CMake项目跨平台、依赖第三方库、团队协作、项目规模较大。学习建议先学 Make理解编译、链接、依赖的基本概念。再学 CMake掌握现代 C/C 项目构建的最佳实践。动手实践从单文件开始逐步增加模块和依赖。参考开源项目学习成熟项目的构建配置写法。十二、进阶资源官方文档GNU Makehttps://www.gnu.org/software/make/manual/CMakeCMake Documentation and Community实战项目用 Make 构建一个简单的命令行工具用 CMake 构建一个跨平台的 GUI 应用为现有项目添加单元测试目标make test配置 CI/CD 流水线自动构建和测试构建工具是 C/C 开发的基石。掌握 Make 和 CMake 不仅能提升开发效率还能让你更好地理解项目的编译、链接和依赖管理过程。