第25章:Makefile与CMake对比:语法对比、可维护性对比、跨平台性对比、适用场景
做嵌入式开发这些年,我一直在跟构建系统打交道。说实话,Makefile和CMake这两个工具,几乎每个嵌入式工程师都绕不开。很多新手会问:「到底学哪个好?」我的回答是:两个都得会,但得知道什么时候用哪个。
这一章,咱们就从语法、可维护性、跨平台性、适用场景四个维度,把这两个工具掰开揉碎对比一下。嗯,我会结合我踩过的坑来讲,希望能帮你少走弯路。
一、语法对比:一个像C,一个像脚本
先看个最简单的例子——编译一个hello.c文件。
Makefile写法
CC = gcc
CFLAGS = -Wall -O2
TARGET = hello
$(TARGET): hello.c
$(CC) $(CFLAGS) -o $@ $<
clean:
rm -f $(TARGET)
CMake写法
cmake_minimum_required(VERSION 3.10)
project(hello)
add_executable(hello hello.c)
看到区别了吗?Makefile用的是制表符缩进来定义规则,CMake用的是函数调用风格。我个人觉得,CMake的语法更接近高级语言,可读性更好。
核心差异点:
- 变量定义:Makefile用
VAR = value,CMake用set(VAR value) - 条件判断:Makefile用
ifeq/ifneq,CMake用if/else/endif - 循环:Makefile用
$(foreach ...),CMake用foreach() - 函数:Makefile用
$(call ...),CMake用function()
我的经验:Makefile的语法太「隐式」了。比如那个$@和$<,新手看了直接懵。CMake的add_executable一眼就知道是干啥的。所以团队协作时,我倾向于用CMake。
二、可维护性对比:谁更抗造?
项目规模一大,构建系统的可维护性就成了关键。我曾经接手过一个有2000行Makefile的老项目……嗯,那感觉就像在拆炸弹。
Makefile的痛点
- 隐式规则太多:自动推导规则虽然方便,但出了问题很难排查
- 变量作用域混乱:
=、:=、?=、+=,四种赋值方式,稍不注意就出bug - 没有模块化机制:只能靠
include,但include的文件多了,依赖关系一团糟 - 调试困难:
make -d输出的信息量太大,根本看不完
CMake的优势
- 清晰的目录结构:每个子目录一个CMakeLists.txt,层次分明
- 目标导向:所有操作围绕target(可执行文件、库)展开
- 内置模块:
find_package、FetchContent等,省去手写查找逻辑 - 生成器表达式:
$<CONFIG:Debug>这种写法,比Makefile的条件判断优雅多了
避坑指南:我曾经在一个项目里用Makefile管理30多个子模块,结果每次改依赖关系都要改半天。后来换成CMake,用target_link_libraries自动传递依赖,世界清净了。
三、跨平台性对比:一次编写,到处编译?
嵌入式开发最头疼的就是跨平台。ARM、x86、RISC-V,Linux、Windows、RTOS……构建系统能不能搞定这些,直接决定了你的工作效率。
| 对比维度 | Makefile | CMake |
|---|---|---|
| 原生支持 | Linux/Unix原生,Windows需额外工具 | 全平台原生支持 |
| 交叉编译 | 手动设置CC/AR/LD等变量 | 通过toolchain文件统一配置 |
| IDE集成 | 需手动配置 | 可生成VS、Xcode、Eclipse等工程 |
| 包管理 | 无内置支持 | FetchContent、CPack、vcpkg |
| 测试支持 | 需手写test目标 | CTest集成,一键运行 |
说白了,Makefile的跨平台能力完全取决于你写Makefile的水平。而CMake把跨平台的复杂性封装起来了——你只需要写一份CMakeLists.txt,它就能在Windows上生成Visual Studio工程,在Linux上生成Makefile,在macOS上生成Xcode工程。
交叉编译实战对比:
Makefile方式:
export CC=arm-none-eabi-gcc
export AR=arm-none-eabi-ar
export LD=arm-none-eabi-ld
make
CMake方式:
# toolchain.cmake
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
# 使用
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake ..
我个人觉得,CMake的toolchain文件机制更优雅。你想想看,把交叉编译的配置单独抽出来,换一个芯片平台就换一个toolchain文件,多清爽。
四、适用场景:什么时候用哪个?
没有银弹。Makefile和CMake各有各的用武之地。我根据实际项目经验,给你画了张决策图。
具体场景建议
- 裸机MCU项目(STM32、ESP32等):我个人习惯用Makefile。因为这类项目通常文件不多,而且需要精细控制编译选项。Makefile的灵活性在这里是优势。
- Linux应用开发:强烈推荐CMake。Linux上的库依赖管理、多目录结构,CMake处理起来得心应手。
- 跨平台库开发:必须用CMake。你想想看,一个库要同时支持Windows、Linux、macOS,用Makefile写条件判断会写到崩溃。
- 快速原型验证:Makefile就够了。我经常在开发板上写个简单的Makefile,三五行就搞定编译。
- 大型团队协作:CMake是唯一选择。它的target依赖图、安装规则、测试集成,都是为团队协作设计的。
我的建议:别把Makefile和CMake对立起来。实际项目中,我经常在CMake里调用Makefile——比如用CMake管理整体项目结构,但某个底层驱动库仍然用Makefile编译。工具是死的,人是活的。
五、总结:没有最好,只有最合适
写到这里,我想起刚入行时的一个项目。当时我用Makefile管理一个包含50多个源文件的嵌入式Linux项目,每次改依赖关系都要小心翼翼。后来换成CMake,用target_sources和target_include_directories,代码结构一目了然。
但反过来,我也见过有人用CMake写一个只有两个文件的hello world项目——嗯,杀鸡用牛刀了。
所以我的建议是:
- Makefile:适合小项目、快速原型、需要精细控制编译选项的场景
- CMake:适合中大型项目、跨平台项目、团队协作的场景
两个都学会,你才能在面对不同项目时,选出最合适的工具。毕竟,嵌入式工程师的核心竞争力不是会用某个工具,而是知道什么时候用什么工具。
公众号:蓝海资料掘金营,微信deep3321