一、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::baseprotobuf::libprotobuf-lite);
    • 系统库(如Android的logzatomic);
  • 作用域:同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 
  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)

四、避坑指南

  1. 路径引用:所有路径用${}包裹,拼接时加双引号(如"${A}/${B}");
  2. 布尔值:CMake中用ON/OFF,而非true/false
  3. 作用域:优先用PRIVATE(仅当前目标生效),避免PUBLIC导致的全局污染;
  4. 第三方库:FetchContent指定GIT_TAG,避免拉取最新代码引发兼容问题;
  5. 错误排查:
    • 头文件找不到:检查target_include_directories
    • 链接错误:检查target_link_libraries(库名是否正确、顺序是否合理)。
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com