# ==================================================
# CMake 学习项目 - 一个文件搞懂核心语法
# ==================================================
# 编译方法:
# cd code
# mkdir build; cd build
# cmake ..
# make
# ./hello
#
# cmake .. → 读取上一级(../)的 CMakeLists.txt,在当前目录生成 Makefile
# make → 读取当前目录的 Makefile,执行编译
# 关闭某个功能试试:
#cmake -DENABLE_LOG=OFF ..
# ==================================================
# ---- [知识点1] 项目声明 ----
cmake_minimum_required(VERSION 3.10) # 当前cmake 要求的最低 版本号
project(cmake_learn C) # 自定义项目名 + 源码后缀如果有C++ , C后面要加CPP
# ---- [知识点2] option: 开关选项(类比 Kconfig) ----
option(ENABLE_LOG "启用日志功能" ON) # 如果不指定默认cmake 变量开关的值,
option(ENABLE_MATH "启用数学模块" ON) #如果指定可以 cmake
# ---- [知识点3] set: 定义变量 ----
set(APP_VERSION "1.0.0")
message(STATUS "版本: ${APP_VERSION}")
message(STATUS "日志: ${ENABLE_LOG}")
message(STATUS "数学: ${ENABLE_MATH}")
# ---- [知识点4] 收集源文件 ----
# main.c 始终编译
set(SOURCES main.c)
# 根据 option 决定是否加入 utils.c(类比 obj-$(CONFIG_X) += xxx.o)
if(ENABLE_LOG)
list(APPEND SOURCES utils.c)
endif()
if(ENABLE_MATH)
list(APPEND SOURCES calc.c)
endif()
# ---- [知识点5] add_executable: 编译可执行文件 ----
add_executable(hello ${SOURCES})
# ---- [知识点6] target_compile_definitions: 添加宏定义 ----
target_compile_definitions(hello PRIVATE APP_VERSION="${APP_VERSION}")
if(ENABLE_LOG)
target_compile_definitions(hello PRIVATE HAS_LOG)
endif()
if(ENABLE_MATH)
target_compile_definitions(hello PRIVATE HAS_MATH)
endif()
# ---- [知识点7] target_compile_options: 编译选项 ----
target_compile_options(hello PRIVATE -Wall)
# ---- [知识点8] add_library: 静态库 ----
add_library(mylib STATIC sensor.c)
target_link_libraries(hello PRIVATE mylib)
--------------------------------------------------------------------------------------------------------
ubuntu@WIN-07G84A33SUO:/mnt/e/Qoder/Cmake/build$ cmake ..
-- The C compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- 版本: 1.0.0
-- 日志: ON
-- 数学: ON
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/e/Qoder/Cmake/build
------------------------------------------------------------------------------------------
Scanning dependencies of target mylib
[ 16%] Building C object CMakeFiles/mylib.dir/sensor.c.o
[ 33%] Linking C static library libmylib.a
[ 33%] Built target mylib
Scanning dependencies of target hello
[ 50%] Building C object CMakeFiles/hello.dir/main.c.o
[ 66%] Building C object CMakeFiles/hello.dir/utils.c.o
[ 83%] Building C object CMakeFiles/hello.dir/calc.c.o
[100%] Linking C executable hello
[100%] Built target hello
------------------------------------------------------------------------------------------
CMake 帮你把这些参数自动分配给每个源文件,不用自己一个个写。
| CMake 指令 | 类比内核 Makefile |
|---|---|
target_compile_definitions(hello PRIVATE HAS_LOG) | ccflags-y += -DHAS_LOG |
target_compile_options(hello PRIVATE -Wall) | ccflags-y += -Wall |
target_link_libraries(hello PRIVATE mylib) | ldflags-y += -lmylib |
核心理解
CMake 就是一个 Makefile 生成器:
CMakeLists.txt (你写的)
│ cmake ..
▼
Makefile + flags.make + link.txt (自动生成的)
│ make
▼
hello (可执行文件)
你写的 CMakeLists.txt 是高层描述,cmake 翻译成底层 Makefile 规则。如果你手写 Makefile,就等于自己写C_FLAGS = -Wall -Dxxx这些;用 CMake 就是让它帮你自动生成,而且跨平台(Windows/Linux 都能生成对应的构建文件)。
-------------------------------------------------------------------------------------------------------------
实际编译命令
make 编译main.c时,实际执行的是:
gcc -Wall -DAPP_VERSION=\"1.0.0\" -DHAS_LOG -DHAS_MATH -c main.c -o main.o │ │ │ │ │ │ │ │ │ └─ 编译哪个文件 │ │ │ └─ C_DEFINES │ │ └─ C_DEFINES │ └─ C_FLAGS逐个解释
| 变量 | 值 | gcc 参数 | 作用 | 具体效果 |
|---|---|---|---|---|
C_FLAGS | -Wall | -Wall | 开启所有常见警告 | 有潜在问题代码时打印警告,不中断编译 |
C_DEFINES | -DAPP_VERSION=\"1.0.0\" | -D | 定义宏,传值 | C 代码里APP_VERSION变成"1.0.0" |
C_DEFINES | -DHAS_LOG | -D | 定义宏,只定义 | C 代码里#ifdef HAS_LOG为真 |
C_DEFINES | -DHAS_MATH | -D | 定义宏,只定义 | C 代码里#ifdef HAS_MATH为真 |
C_INCLUDES | (空) | -I | 指定头文件搜索路径 | 当前为空,用默认路径 |
实际效果对比
-Wall的作用:
// 没有 -Wall:编译通过,不提示
// 有 -Wall:编译时会打印警告
int x;
printf("%d", x); // 警告:变量 x 未初始化就使用了
-D有值 vs 无值的区别:
// -DAPP_VERSION=\"1.0.0\" → 有值,可以当字符串用
printf("版本: %s\n", APP_VERSION); // 输出:版本: 1.0.0
// -DHAS_LOG → 无值,只表示"已定义"
#ifdef HAS_LOG
log_print("日志已启用"); // 这段代码会被编译
#endif
-I头文件路径(当前为空):
# 如果有这一行: target_include_directories(hello PRIVATE /opt/mylib/include)# C_INCLUDES 就会变成: C_INCLUDES = -I/opt/mylib/include这样#include "xxx.h"时,gcc 会去那个目录找头文件。你现在项目头文件都在同目录,所以不需要。
----------------------------------------------------------------
最终可以执行
ubuntu@WIN-07G84A33SUO:/mnt/e/Qoder/Cmake/build$./hello
=== CMake 学习 Demo (v1.0.0) ===
[LOG] 日志功能已启用
add(3, 5) = 8
mul(4, 7) = 28
温度: 25.6 C (来自静态库 mylib)