学习笔记,CMake配置、管理子项目等使用详解

884 阅读14分钟

CMake的配置与使用详解

前言

对应 CMake 大家都不陌生,在前文中我们也简单讲了 C/C++ 项目的编译,Makefile 的必要性,以及 CMake 的便捷性。

简单来说,Makefile 是一个包含编译规则的文本文件,用于定义如何编译和链接程序的不同部分。在多文件 C/C++ 项目中,Makefile 指定了源文件、编译器、编译选项以及目标文件的依赖关系,从而使得编译过程可以自动化。

而 Makefile 写起来适配什么的太麻烦,我们就可以用 CMake 来简化这个过程。CMake 本身并不直接编译代码,而是通过分析项目的结构和配置文件(CMakeLists.txt)来生成相应的构建系统文件。对于类 Unix 系统,CMake 通常生成 Makefile,这些 Makefile 将被 make 工具使用来执行编译过程。

image.png

本篇文章我们就探讨一下 CMake 的配置与使用,分为几个主要部分。

首先,我们将介绍如何安装CMake并构建项目,帮助您快速入门这个工具;接着,我们将详细解析CMake的基本语法和配置方法,特别是如何编写和优化 CMakeLists.txt 文件,以满足不同项目的需求;此外,我们还将探讨如何使用CMake的目标管理项目,从而提高代码的模块化和可重用性。

一、安装CMake并构建项目

在本章节中,我们将介绍如何在 macOS 系统下安装 CMake,以及如何使用 CMake 创建和构建一个简单的 C/C++ 项目。通过这个过程,您将能够了解 CMake 的基本使用方法,并为后续的深入学习打下基础。

首先在 macOS 上,安装 CMake 可以通过多种方式进行,这里我是用的 Homebrew 安装的:

brew install cmake
cmake --version

安装之后可以查看版本,是否安装成功。

在安装完成 CMake 后,您可以创建一个简单的 C/C++ 项目来测试 CMake 是否正常工作。

使用 CMake 来构建一个项目,其实就是三个步骤,配置,生成,编译。

我们先创建对应的文件和对应的 C/C++ 文件和对应的 CMakeList.txt 配置。

源码如下:

print.h:

#include <iostream>
#include <ctime>

using namespace std;
void log(const string& message);

print.cpp :

#include "../include/print.h"

void log(const string& message) {
    
    // 获取当前日期和时间
    time_t now = time(0);
    struct tm *localTime = localtime(&now);

    // 格式化日期和时间
    char buffer[80];
    strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", localTime);

    // 输出日志信息
    cout << "[" << buffer << "] " << message << endl;
}

功能就是一个很简单的 Log 打印,前缀加上当前时间。

主入口 hello.cpp :

#include<iostream>
#include "include/print.h"

int main(){
    std::cout << "Hello World!" << std::endl;  
    log("This is a log message.");//调用的print的方法
}

重点就是配置文件 CMakeLists.txt :

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 


# 用于创建一个名为 "hello" 的可执行文件,它由 hello.cppsrc/log.cpp 两个源文件构建而成
add_executable(hello hello.cpp src/print.cpp) 

接下来就是构建编译了,分为两部,一步是预构建创建对应的 build 文件夹,第二步是真正的构建。

预构建:

cmake -B build

cmake -B build 创建build文件夹,但是此时还没有真正开始build,生成完成之后我们的文件目录如下:

其中 MakeFile 是在 Linux 环境下 C/C++ 程序工程管理文件CMakeCache.txt 是由 CMake 生成的一个重要文件,用于存储 CMake 配置过程中生成的各种变量、路径和选项的信息。

接下来才是真正的构建。

cmake --build build

cmake --build 根据生成的构建系统文件来实际构建项目,包括编译源代码、链接库文件等操作。

build 参数指定了构建系统文件所在的目录。

打印日志如下:

在目录中我们就可以看到多出来的编译好的文件了

这里我直接运行试试

这样就完成了,是不是很方便呢,自动化程度算很高了。

当然网上有很多其他的命令和我这种不一样,但是思路都是一直的,预构建之后再构建,比如:

手动命令生成 build 文件夹

mkdir build
cd build

然后在 build 文件夹中使用 CMake 生成构建文件:

cmake ..

然后编译项目

make

此时,CMake 将会根据 CMakeLists.txt 的配置生成 Makefile 并编译项目。成功编译后,您将在构建目录中看到生成的可执行文件。

其实都是同一个流程只是命令不同,而我们需要关注的只是 CMakeList 配置文件,内部涉及到的一些语法与配置才是我们需要重点关注的。

二、CMake的语法与配置

在本章节中,我们将深入探讨 CMake 的基本语法、 CMakeLists.txt 文件的配置与命令,以及如何使用 CMake 的目标(Targets)管理项目。通过这些内容,您将能够更高效地使用 CMake 来管理和构建复杂的 C/C++ 项目。

2.1 CMake的语法

CMake 采用一种简单而易于理解的语法来描述构建过程。基本的语法下面单独介绍一下:

注释:

这个没什么说的,大家都能理解,这里的 message 命令是打印消息,类似Log的功能。

# This is a line comment \n hh # [].
message("First Argument\n" # This is a line comment :)
        "Second Argument") # This is a line comment.
# set(CMAKE_CXX_STANDARD 14)
# set(CMAKE_CXX_STANDARD_REQUIRED True)

变量: 使用 set() 命令定义和使用变量,变量名通常以大写字母表示

它的变量控制还是和我们普通的开发语言很不一样

# 变量引用
message("CMAKE_MINOR_VERSION:${CMAKE_MINOR_VERSION}")
set(HELLO "HELLO")
set(HELLO_WROLD "Hello, World!")
message("${HELLO}")
message("${${HELLO}_WROLD}")

unset()

条件控制: 使用 if()、elseif()、else() 和 endif() 来控制逻辑流

和普通开发语言有点不一样,但是相信大家也能看懂

# 条件语句
set(NUM 10)

if(NUM LESS 5)
    message("NUM 小于 5")
elseif(NUM EQUAL 5)
    message("NUM 等于 5")
else()
    message("NUM 大于 5")
endif()

**循环:**使用 foreach() 和 endforeach() 来实现循环迭代

和普通开发语言有点不一样,但是相信大家也能看懂,总之是任何逻辑都需要一个 end 的操作符

# 循环语句
set(my_list 1 2 3 4 5)

foreach(item IN LISTS my_list)
    if(item STREQUAL "5")
        message("遇到了5,终止循环")
        break()
    endif()

    if(item STREQUAL "4")
        message("遇到了${item},跳过这次迭代")
        continue()
    endif()

    message("当前项为: ${item}")
endforeach()

while 也一样可以用哦

set(counter 0)

while(counter LESS 5)
    math(EXPR counter "${counter}+1")
    if(counter EQUAL 3)
        message("遇到了3,跳过此次迭代")
        continue()
    endif()
    message("当前计数: ${counter}")
endwhile()

**集合:**增删改查逻辑 list(APPEND) list(GET) list(REMOVE_ITEM)

其实有一点变量赋值的意思,只是需要多个参数,大家一看就明白,根据第一个参数的关键字来判断的

# 创建一个包含字符串的列表
set(my_list "apple" "banana" "cherry")

# 遍历列表中的元素并输出
foreach(item IN LISTS my_list)
    message("Item: ${item}")
endforeach()

# 向列表添加元素
list(APPEND my_list "date")

# 输出更新后的列表
message("Updated List: ${my_list}")

# 获取列表长度
list(LENGTH my_list list_length)
message("List Length: ${list_length}")

# 获取列表指定索引处的元素
list(GET my_list 1 element)
message("Element at index 1: ${element}")

# 删除列表中的元素
list(REMOVE_ITEM my_list "banana")

# 输出更新后的列表
message("List after removing 'banana': ${my_list}")

其他的一些不常用操作:

Reading
  list(LENGTH <list> <out-var>)
  list(GET <list> <element index> [<index> ...] <out-var>)
  list(JOIN <list> <glue> <out-var>)
  list(SUBLIST <list> <begin> <length> <out-var>)

Search
  list(FIND <list> <value> <out-var>)

Modification
  list(APPEND <list> [<element>...])
  list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
  list(INSERT <list> <index> [<element>...])
  list(POP_BACK <list> [<out-var>...])
  list(POP_FRONT <list> [<out-var>...])
  list(PREPEND <list> [<element>...])
  list(REMOVE_ITEM <list> <value>...)
  list(REMOVE_AT <list> <index>...)
  list(REMOVE_DUPLICATES <list>)
  list(TRANSFORM <list> <ACTION> [...])

Ordering
  list(REVERSE <list>)
  list(SORT <list> [...])

函数: 它居然也有函数?只是不常用而已

一般定义函数以作为命令调用

function(<name> [<arg1> ...])
  <commands>
endfunction()

定义一个名为 的函数,它接受名为 , ... 的参数,调用函数执行 语句;在调用该函数之前它们不会被执行。

例如我们要编写一个函数,该函数用于打印指定数量的消息。我们将定义一个名为 print_message 的函数,它接受一个字符串消息和一个整数,表示要打印该消息的次数。

# 定义函数
function(print_message msg count)
    # 循环打印消息
    foreach(i RANGE 1 ${count})
        message(STATUS "${msg} - ${i}")
    endforeach()
endfunction()

# 调用函数
print_message("Hello, CMake!" 5)

这样就会遍历打印5次

文件 File 操作:

我们理解了函数的概念,再看 List 的操作,Flie 的操作是不是就很容易理解了,下面是内置的一些 File 操作的函数。

Reading
  file(READ <filename> <out-var> [...])
  file(STRINGS <filename> <out-var> [...])
  file(<HASH> <filename> <out-var>)
  file(TIMESTAMP <filename> <out-var> [...])
  file(GET_RUNTIME_DEPENDENCIES [...])

Writing
  file({WRITE | APPEND} <filename> <content>...)
  file({TOUCH | TOUCH_NOCREATE} <file>...)
  file(GENERATE OUTPUT <output-file> [...])
  file(CONFIGURE OUTPUT <output-file> CONTENT <content> [...])

Filesystem
  file({GLOB | GLOB_RECURSE} <out-var> [...] <globbing-expr>...)
  file(MAKE_DIRECTORY <directories>...)
  file({REMOVE | REMOVE_RECURSE } <files>...)
  file(RENAME <oldname> <newname> [...])
  file(COPY_FILE <oldname> <newname> [...])
  file({COPY | INSTALL} <file>... DESTINATION <dir> [...])
  file(SIZE <filename> <out-var>)
  file(READ_SYMLINK <linkname> <out-var>)
  file(CREATE_LINK <original> <linkname> [...])
  file(CHMOD <files>... <directories>... PERMISSIONS <permissions>... [...])
  file(CHMOD_RECURSE <files>... <directories>... PERMISSIONS <permissions>... [...])

Path Conversion
  file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])
  file(RELATIVE_PATH <out-var> <directory> <file>)
  file({TO_CMAKE_PATH | TO_NATIVE_PATH} <path> <out-var>)

Transfer
  file(DOWNLOAD <url> [<file>] [...])
  file(UPLOAD <file> <url> [...])

Locking
  file(LOCK <path> [...])

Archiving
  file(ARCHIVE_CREATE OUTPUT <archive> PATHS <paths>... [...])
  file(ARCHIVE_EXTRACT INPUT <archive> [...])

2.2 CMakeLists.txt 配置与命令

上面我们介绍了 CMake 的基础语法,下面我们介绍一下 CMake 的基础常用命令

  1. cmake_minimum_required:指定 CMake 的最低版本要求。
cmake_minimum_required(VERSION 3.10)

2. project:定义项目的名称和语言。

project(MyProject LANGUAGES CXX)

3. add_executable 声明一个可执行文件及其源文件,CMake 将使用指定的源文件编译出可执行文件。

add_executable(MyExecutable main.cpp)

4. add_library 声明一个库(静态库或共享库)。库可以被其他目标(如可执行文件)链接。

add_library(MyLibrary STATIC lib.cpp)

5. target_link_libraries 将库链接到目标可执行文件或库,确保编译时能够找到所需的库。

target_link_libraries(MyExecutable MyLibrary)

6. include_directories 指定要包含的头文件目录。

include_directories(include)

示例:

cmake_minimum_required(VERSION 3.10)

# 定义项目名称和编程语言
project(MyProject LANGUAGES CXX)


# 设置头文件目录
include_directories(include)

# 添加静态库
add_library(MyLibrary STATIC 
    src/static_helper.cpp
)

# 添加共享库
add_library(MySharedLibrary SHARED 
    src/shared_helper.cpp
)

# 设置源文件列表
set(SOURCE_FILES
    src/main.cpp
    src/helper.cpp
)

# 添加可执行文件
add_executable(MyExecutable ${SOURCE_FILES})

# 链接库到可执行文件
target_link_libraries(MyExecutable MyLibrary MySharedLibrary)

三、使用 CMake 管理子模块的几种方式

在实际开发中我们或多或少会用到其他的库或模块,比如Log,比如图片分析相关的 OpenCV。那么我们的主项目如何依赖这些子模块呢?有哪些方式呢?

第一种方案其实就是直接把源码导入到自己的目录,直接依赖的时候写过的相对路径,直接写死。但是缺点就是不好修改,不好模块化,不够优雅。

第二种方案就是把对应的模块单独分为文件夹,直接管理它的源码,比如我的Hello项目依赖其中一个log的库,那么我就能导入log的源码,直接依赖。

我可以使用 log.cmake 中配置当库的源码路径:

set(log_sources log/src/log.cpp)

在主项目 hello 的 CMakeList 配置中:

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

# 引入Log库中的变量
include(log/log.cmake)
# 打印指定的变量为源码路径
message(${log_sources})

# 用于创建一个名为 "hello" 的可执行文件,它由 hello.cpp 和 log/src/log.cpp 两个源文件构建而成
add_executable(hello hello.cpp ${log_sources}) 

我们就可以直接引用Log库中的配置文件的变量。接下来就是CMake的编译流程,还是之前的逻辑。

第三种方案,CMakeList 的互相依赖之 add_subdirectory

第二种方案是添加的配置文件,这里我们直接在 LOG 库中添加 CMakeList.txt 配置文件

# 指定源文件,创建 log_lig 库
add_library(log_lib src/log.cpp)

在工程根目录下的 CMakeLists.txt 文件里,引入 log_lig 库

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

add_executable(hello hello.cpp) 

# 引入整体的Log源码文件
add_subdirectory(log)
# 链接 log_lib 库
target_link_libraries(hello PUBLIC log_lib)
# 链接到 log_lib 库的头文件搜索路径,注意类似 PROJECT_BINARY_DIR 这些变量是CMake 预设的一些常用变量
target_include_directories(hello PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/log/include")

CMake的编译方式还是之前的一样

最终编译出来的产物就是:

一个.a的库文件和一个hello的执行文件。

这里说一下题外话,扩展一下关于 include_directories 与搜索路径的这个概念。

在 hello.cpp 的include 中我们在使用 log_lib 库中的头文件的时候我们就不是之前的

#include<iostream>
#include "log/include/log.h" 

这种全路径了,如果导入一个库还得自己查头文件的路径,一个两个我还能查,要是导入的库比较大或者导入很多库,那我不得烦死。所以我们常用的是直接

#include<iostream>
#include "log.h"   //大伙也知道 使用 "" 引用的时候会在本地查找,那么直接引用头文件(路径由CMake自动处理)

这样使用 ”“ 的方式导入,Cmake会自动帮我们查找,但是在哪查呢?我们得告诉 CMake 。主流是两种方式:

第一种是在hello 也就是项目的根目录的 CMakeLists 的配置中告诉 CMake

# 链接到 log_lib 库的头文件搜索路径,注意类似 PROJECT_BINARY_DIR 这些变量是CMake 预设的一些常用变量
target_include_directories(hello PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/log/include")

你去这里找头文件。这是很方便的。

还有一种方式是在 lib_log 库中指定让依赖我的宿主可以访问:

# 注意和 add_executable 的区别,这里指定为库,创建 log_lig 库
add_library(log_lib src/log.cpp)


# 声明头文件目录并设置为PUBLIC属性,配置了此属性才可以在当其他项目(如hello)链接 log_lib 时,自动获取头文件搜索路径
target_include_directories(log_lib
    PUBLIC 
        # 允许其他项目自动继承此路径
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>  
        $<INSTALL_INTERFACE:include>
)

那么此时我们就无需在我们的根目录中指定 target_include_directories 属性了。

亲测两种方案都是可以的,但是我们用的最多的那肯定是在我们自己的项目中指定了,毕竟其他人写的第三方的插件并不会主动暴露,所以我们自己指定好目录是最方便的。

大纲不好,有点乱,因为写的时候才发现这里是有差异的,需要额外说明,但是又不需要额外搞一个章节,就这样吧。

第四种方案,CMakeList 的互相依赖之 Object 依赖

与上面的有一些差异,这里我们需要主动的指定 log 库中的 CMakeLists 配置为 Object :

add_library(log_lib OBJECT src/log.cpp)

target_include_directories(log_lib PUBLIC include)

在 hello 项目的根目录的 CMakeLists 的配置中设置 :

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

add_executable(hello hello.cpp) 

# 引入整体的Log源码文件
add_subdirectory(log)
# 链接 log_lib 库
target_link_libraries(hello PUBLIC log_lib)

相比前面的方案来说少了 target_link_libraries 链接库和 target_include_directories 链接库的头文件配置。

这样也是可以运行的:

区别就是这种方式可以把 log 库作为一个源文件进行链接,生成一个源文件,而前者是生成2个源文件。

第五种方案 静态库.a文件与动态库.so文件的依赖

对于移动端开发的小伙伴来说,我们有时候并不是直接拿源码来调试的,而是 Native 的同学直接给我们动态库或静态库,有些第三放的库也回直接给出编译好的动态库或静态库,那么我们如何使用呢?

我们分别把静态库和动态库复制到指定的目录,由于我是mac系统所以打出的动态库是dylib后缀,如果是linux系统是so,我没有交叉编译先这么用,流程是一样的。

在 hello 项目的根目录的 CMakeLists 的配置:

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

# 引入头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 链接静态库
link_directories(${PROJECT_SOURCE_DIR}/a)
link_libraries(log_lib)


add_executable(hello hello.cpp) 

都是标准流程,直接编译:

对比我们的编译阶段快了也少了,因为我们不需要再去编译 log 库了。

那么如何使用动态库呢?

其他的不变,我们在 hello 根目录的 CMakeLists 配置中修改配置:

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

# # 引入头文件
# include_directories(${PROJECT_SOURCE_DIR}/include)
# # 链接静态库
# link_directories(${PROJECT_SOURCE_DIR}/a)
# link_libraries(log_lib)


# 引入头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 声明库目录
link_directories(${PROJECT_SOURCE_DIR}/so)

add_executable(hello hello.cpp) 

# 链接动态库给当前程序
target_link_libraries(hello PUBLIC log_lib)

可以看到,动态库是我们手动的链接到 hello 主程序中的,和静态库的使用方法稍微不同。

那么编译之后的运行效果是差不多的:

移动开发中,例如安卓的开发中我们常见的场景还是动态库,所以这里就有参考意义。

那么再扩展一下,有了解的同学可能知道,在 AndroidStudio 工具中开发 Native 项目的时候,一般是不用 link_directories 这个命令的,因为 Android 开发引用的 so 动态库一般都会导入32为和64位,它们不在一个目录中,所以我们多是用 set_target_properties 的方式来解决32为64位不同文件夹的问题,如:

add_library(fdk-aac
        SHARED
        IMPORTED)
set_target_properties(
        fdk-aac
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

那么说回本文的示例,我们也可以这么使用:

# 指定项目所需的最低CMake版本
cmake_minimum_required(VERSION 3.21) 
# 用于定义项目的名称和支持的编程语言。在这里,项目名称被指定为 "hello",并且指定了使用 C++ 语言(CXX)来编写项目
project(hello CXX) 

add_executable(hello hello.cpp)

# 如何使用静态库
# # 引入头文件
# include_directories(${PROJECT_SOURCE_DIR}/include)
# # 链接静态库
# link_directories(${PROJECT_SOURCE_DIR}/a)
# link_libraries(log_lib)


# 如何使用动态库,方式一
# # 引入头文件
# include_directories(${PROJECT_SOURCE_DIR}/include)

# # 声明库目录
# link_directories(${PROJECT_SOURCE_DIR}/so)

# # 链接动态库给当前程序
# target_link_libraries(hello PUBLIC log_lib)


# 如何使用动态库,方式二
# 引入头文件
include_directories(${PROJECT_SOURCE_DIR}/include)

# 声明库的明确完整路径
add_library(log_lib
        SHARED
        IMPORTED)
set_target_properties(
        log_lib
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/so/liblog_lib.dylib)


# 链接动态库给当前程序
target_link_libraries(hello PUBLIC log_lib)

除此之外还有 Android 开发特有的指定动态库的搜索路径的命令

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}../../../../libs/${ANDROID_ABI}")

比如我们的so文件放在根目录的libs文件下面,我们就直接指定路径这样也是可以找到对应的动态库文件的。

这几种方案大家任选其一即可。

那么关于 CMake 管理子模块的五中方式大家都学会了吗?

如果是搞移动开发的安卓项目的同学特别要掌握第三种和第五种方式哦。

总结

在本篇文章中,我们详细探讨了 CMake 的配置与使用,涵盖了从安装到复杂项目管理的多个方面。CMake 作为一种强大的构建系统生成工具,能够显著简化 C/C++ 项目的构建过程。我们首先安装了 CMake 并创建了一个简单的 C/C++ 项目,通过简单的示例引导读者快速入门。

接着,我们深入分析了 CMake 的基本语法和配置方法,包括变量定义、条件控制、循环、集合操作以及文件操作等内容。我们还介绍了常用的 CMakeLists.txt 命令,如 project、add_executable、add_library 和 target_link_libraries,帮助大家理解如何构建和链接项目目标。

在使用 CMake 管理子模块的部分,我们提出了多种方案,从直接将源码引入到项目中,到使用 add_subdirectory 或 target_link_libraries 进行模块化管理,每种方案都有其适用场景和优缺点。这使得读者可以根据项目需求选择最合适的管理方式,提升代码的模块化和可重用性。

最后,我们讨论了移动开发中特有的处理动态库和静态库的方法,强调了在 Android 开发中使用 CMake 的重要性。通过这些内容我们不仅掌握了 CMake 的基本用法,还能灵活应用 CMake 来解决实际开发中的问题。

在后续的文章中,我也将提供一些实际的 CMake 案例结合实际项目需求进行演示。

文章的结构比较混乱,是我大纲没做好,写着写着发现就觉得有很多需要扩展一下的地方,比如CMake的内置常量,搜索路径的配置,安卓项目的CMake配置异同等。

当然如果在阅读过程中遇到任何代码或理解有误解的地方,或者在代码、注释中发现错误和遗漏,都可以在评论区指出并进行修正。

如果您觉得本文对您有启发和帮助,请给我一个点赞以示支持!您的反馈才是我最大的动力。

Ok,完结撒花啦。