一、CMake 核心基础(必学,占80%使用场景)
1. 版本声明(脚本第一行必写)
命令格式
cmake_minimum_required(VERSION <版本号>)
示例(你的代码)
cmake_minimum_required(VERSION 3.10) # Abseil项目最低版本
cmake_minimum_required(VERSION 3.22.1) # nativesdk项目最低版本
cmake_minimum_required(VERSION 3.5) # utf8_range项目最低版本
作用
指定运行当前CMake脚本所需的最低CMake版本,低于该版本会直接报错,确保脚本兼容性。
2. 项目声明
命令格式
project(<项目名> [LANGUAGES <语言列表>] [VERSION <版本号>])
示例(你的代码)
project(absl LANGUAGES CXX VERSION 20230802) # 仅C++,指定版本
project(nativesdk) # 默认语言(C/C++),无版本
project(utf8_range C CXX) # 同时支持C和C++
作用
- 定义项目名称,是CMake脚本的核心标识;
LANGUAGES:指定项目支持的编译语言(CXX=C++,C=C,不写则默认C/C++);VERSION:给项目标注版本号(仅发布库时有用);
自动生成变量(常用)
${PROJECT_NAME}:当前项目名(如absl、nativesdk);${PROJECT_SOURCE_DIR}:项目源码根目录。
3. 变量设置(存储路径/开关/参数)
命令格式
# 基础赋值
set(<变量名> <变量值>)
# 缓存变量(强制覆盖默认值)
set(<变量名> <值> CACHE <类型> "<描述>" FORCE)
示例(你的代码)
# 基础变量:编译标准、平台配置
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_ANDROID_STL_TYPE c++_static)
# 缓存变量:关闭第三方库测试/示例
set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(protobuf_USE_LITE_RUNTIME ON CACHE BOOL "" FORCE)
# 路径变量:拼接路径
set(UTF8_RANGE_SRC_DIR "${protobuf_SOURCE_DIR}/third_party/utf8_range")
核心说明
- 变量引用方式:
${变量名}(如${CMAKE_CXX_STANDARD}); - 常用类型:
BOOL(布尔值,值为ON/OFF,非true/false); FORCE:强制覆盖CMake默认的变量值(比如关闭Abseil的测试编译);- 路径拼接:用双引号包裹(
"${A}/${B}"),避免路径含空格时出错。
4. 编译库/可执行文件(核心编译命令)
(1)编译库:add_library
命令格式
add_library(<库名> <库类型> <源码文件列表>)
示例(你的代码)
# 静态库(STATIC):生成.a/.lib文件
add_library(utf8_range STATIC naive.c range2-neon.c)
# 动态库(SHARED):生成.so/.dll文件
add_library(nativesdk SHARED ${ALL_SRC})
核心说明
- 库类型:
STATIC:静态库,编译时打包进可执行文件,不依赖外部库;SHARED:动态库,运行时加载,需配套发布.so/.dll;INTERFACE:仅头文件库(无源码,仅提供头文件);
- 源码列表:可直接写文件名(如
naive.c),也可引用变量(如${ALL_SRC})。
(2)编译可执行文件:add_executable
命令格式
add_executable(<程序名> <源码文件列表>)
示例(你的代码)
add_executable(tests utf8_validity_test.cc) # 编译测试程序
作用
生成可运行的程序(如测试程序、工具程序),仅在需要可执行文件时使用。
5. 头文件目录配置
命令格式
# 全局头文件(所有目标生效)
include_directories(<目录列表>)
# 目标专属头文件(推荐,精准)
target_include_directories(<目标名> <作用域> <目录列表>)
示例(你的代码)
# 全局头文件
include_directories(${UTF8_RANGE_SRC_DIR})
# 目标专属头文件(PRIVATE仅当前目标可用)
target_include_directories(nativesdk PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${abseil-cpp_SOURCE_DIR}
)
核心说明
- 作用域:
PRIVATE:仅当前目标可用(如nativesdk自己的头文件);PUBLIC:当前目标 + 依赖该目标的其他目标可用;INTERFACE:仅依赖该目标的其他目标可用;
- 核心作用:告诉编译器「去哪里找#include的头文件」,避免「头文件找不到」错误。
6. 库链接(关联依赖库)
命令格式
target_link_libraries(<目标名> <作用域> <库名列表>)
示例(你的代码)
target_link_libraries(nativesdk PRIVATE
absl::base absl::strings
protobuf::libprotobuf-lite
utf8_range
log z atomic m
)
target_link_libraries(utf8_validity PUBLIC absl::strings)
核心说明
- 作用:告诉编译器「当前目标需要链接哪些库」,解决编译时的「未定义符号」错误;
- 库名类型:
- 自定义库(如
utf8_range); - 第三方库别名(如
absl::base、protobuf::libprotobuf-lite); - 系统库(如Android的
log、z、atomic);
- 自定义库(如
- 作用域:同
target_include_directories(PRIVATE/PUBLIC/INTERFACE)。
7. 条件判断(控制编译逻辑)
命令格式
if(<条件>)
# 满足条件执行的命令
elseif(<其他条件>)
# 其他条件执行的命令
else()
# 都不满足执行的命令
endif()
示例(你的代码)
# 检查是否存在目标
if (NOT TARGET absl::strings)
if (NOT ABSL_ROOT_DIR)
find_package(absl REQUIRED CONFIG)
else ()
add_subdirectory(${ABSL_ROOT_DIR} third_party/abseil-cpp)
endif ()
endif ()
# 检查编译开关
if (${ABSL_BUILD_DLL})
absl_make_dll()
endif ()
常用条件
NOT:取反(如NOT TARGET absl::strings= 不存在absl::strings目标);MATCHES:字符串匹配(如"${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang");- 变量判断(如
${ABSL_BUILD_DLL}= ON时执行);
作用
实现「跨平台编译」「按需编译」(比如Android和Linux编译逻辑不同、是否编译测试代码)。
二、CMake 进阶命令(你的代码高频使用)
1. 第三方库拉取:FetchContent(自动下载/编译)
命令组合
# 声明要拉取的库
FetchContent_Declare(
<库名>
GIT_REPOSITORY <Git仓库地址>
GIT_TAG <版本标签/分支>
GIT_SHALLOW <ON/OFF>
)
# 初始化并编译库
FetchContent_MakeAvailable(<库名1> <库名2>)
示例(你的代码)
FetchContent_Declare(
abseil-cpp
GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
GIT_TAG 20240116.0
GIT_SHALLOW OFF
)
FetchContent_MakeAvailable(abseil-cpp protobuf)
核心说明
- 作用:无需手动下载第三方库源码,CMake自动从Git拉取、编译,简化依赖管理;
GIT_TAG:必须指定(如版本号、标签),避免拉取最新代码导致兼容问题;- 拉取的库会存到构建目录的
_deps子目录下。
2. 查找系统库:find_package(找已安装的库)
命令格式
find_package(<库名> [REQUIRED] [CONFIG])
示例(你的代码)
find_package(Threads REQUIRED) # 查找线程库,找不到则报错
find_package(GTest REQUIRED) # 查找GoogleTest库
find_package(absl REQUIRED CONFIG) # 按配置文件查找Abseil
核心说明
REQUIRED:标记为必选依赖,找不到库则直接报错;CONFIG:按库的CMake配置文件查找(而非系统默认路径);- 区别:
FetchContent是「下载并编译」,find_package是「找已安装的库」。
3. 子目录编译:add_subdirectory
命令格式
add_subdirectory(<子目录路径> [编译输出目录])
示例(你的代码)
add_subdirectory(absl) # 编译Abseil主目录
add_subdirectory(base) # 编译Abseil的base子模块
add_subdirectory(${ABSL_ROOT_DIR} third_party/abseil-cpp) # 指定输出目录
作用
进入子目录,执行该目录下的CMakeLists.txt,适合拆分大型项目(如Abseil按模块拆分编译)。
4. 编译宏定义:target_compile_definitions
命令格式
target_compile_definitions(<目标名> <作用域> <宏名1> <宏名2=值>)
示例(你的代码)
target_compile_definitions(nativesdk PRIVATE
PROTOBUF_USE_LITE_RUNTIME
GOOGLE_PROTOBUF_NO_RTTI
PROTOBUF_DISABLE_JSON=1
)
作用
给编译器传递宏定义,代码中可通过#ifdef判断(如PROTOBUF_USE_LITE_RUNTIME启用Protobuf Lite版本)。
5. 文件批量查找:file(GLOB)
命令格式
file(GLOB <变量名> <文件匹配规则>)
示例(你的代码)
file(GLOB ALL_SRC
${CMAKE_CURRENT_SOURCE_DIR}/core/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/proto/*.pb.cc
)
核心说明
- 作用:批量匹配文件(如所有
.cpp、.pb.cc),避免手动罗列所有源码; - 匹配规则:
*.cpp(所有cpp文件)、core/*.cpp(core目录下的cpp文件); - 注意:新增文件后需重新执行
cmake ..才能识别。
6. CMake策略配置:cmake_policy
命令格式
cmake_policy(SET <策略ID> <NEW/OLD>)
示例(你的代码)
cmake_policy(SET CMP0025 NEW) # Apple Clang编译器标识修正
cmake_policy(SET CMP0077 NEW) # option命令尊重变量值
作用
兼容不同CMake版本的行为差异(如旧版本CMake的某些命令逻辑和新版本不同,通过策略统一)。
三、CMake 实战场景(结合你的代码)
场景1:编译Android动态库(nativesdk)
核心命令流程:
# 1. 基础配置
cmake_minimum_required(VERSION 3.22.1)
project(nativesdk)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_ANDROID_STL_TYPE c++_static)
# 2. 拉取第三方依赖
FetchContent_Declare(abseil-cpp ...)
FetchContent_Declare(protobuf ...)
FetchContent_MakeAvailable(abseil-cpp protobuf)
# 3. 收集源码
file(GLOB ALL_SRC core/*.cpp jni/*.cpp proto/*.pb.cc)
# 4. 编译动态库
add_library(nativesdk SHARED ${ALL_SRC})
# 5. 配置头文件
target_include_directories(nativesdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${abseil-cpp_SOURCE_DIR})
# 6. 定义编译宏
target_compile_definitions(nativesdk PRIVATE PROTOBUF_USE_LITE_RUNTIME)
# 7. 链接依赖库
target_link_libraries(nativesdk PRIVATE absl::base protobuf::libprotobuf-lite)
场景2:编译静态库(utf8_range)
核心命令流程:
# 1. 基础配置
cmake_minimum_required(VERSION 3.5)
project(utf8_range C CXX)
# 2. 编译静态库
add_library(utf8_range STATIC naive.c range2-neon.c)
# 3. 查找/编译Abseil依赖
if (NOT TARGET absl::strings)
find_package(absl REQUIRED CONFIG)
endif()
# 4. 链接Abseil
target_link_libraries(utf8_validity PUBLIC absl::strings)
四、避坑指南
- 路径引用:所有路径用
${}包裹,拼接时加双引号(如"${A}/${B}"); - 布尔值:CMake中用
ON/OFF,而非true/false; - 作用域:优先用
PRIVATE(仅当前目标生效),避免PUBLIC导致的全局污染; - 第三方库:
FetchContent指定GIT_TAG,避免拉取最新代码引发兼容问题; - 错误排查:
- 头文件找不到:检查
target_include_directories; - 链接错误:检查
target_link_libraries(库名是否正确、顺序是否合理)。
- 头文件找不到:检查