0%

vscode中使用cmake和clangd

参考:

文章链接: https://subingwen.cn/cmake/CMake-primer/

文章作者: 苏丙榅

1.CMake

1.1 基本使用语法

1
2
3
cmake_minimum_required(VERSION 3.0)
project(CALC)
add_executable(app add.c div.c main.c mult.c sub.c)
  • cmake_minimum_required:指定使用的 cmake 的最低版本

    • 可选,非必须,如果不加可能会有警告
  • project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

    1
    2
    3
    4
    5
    6
    7
    # PROJECT 指令的语法是:
    project(<PROJECT-NAME> [<language-name>...])
    project(<PROJECT-NAME>
    [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
    [DESCRIPTION <project-description-string>]
    [HOMEPAGE_URL <url-string>]
    [LANGUAGES <language-name>...])
  • add_executable:定义工程会生成一个可执行程序

    1
    add_executable(可执行程序名 源文件名称)
    • 源文件名可以是一个也可以是多个,如有多个可用空格或;间隔

      1
      2
      3
      4
      # 样式1
      add_executable(app add.c div.c main.c mult.c sub.c)
      # 样式2
      add_executable(app add.c;div.c;main.c;mult.c;sub.c)

一般我们会在项目根目录下新建顶层 CMakeLists.txt 文件,然后执行:

1
2
3
4
mkdir build
cd build
cmake ..
make

将 CMake 生成的各自文件单独放在 build 目录中,并完成编译。

1.2 使用set

set 函数的定义如下:

1
2
3
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

1.2.1 定义变量

1
2
3
4
5
6
# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c div.c main.c mult.c sub.c)

# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app ${SRC_LIST})

1.2.2 指定使用的C++标准

在 CMake 中有两种方式:

  1. 在 CMakeLists.txt 中通过 set 命令指定

    1
    2
    3
    4
    5
    6
    7
    8
    #增加-std=c++11
    set(CMAKE_CXX_STANDARD 11)
    #增加-std=c++14
    set(CMAKE_CXX_STANDARD 14)
    #增加-std=c++17
    set(CMAKE_CXX_STANDARD 17)

    set(CMAKE_CXX_STANDARD_REQUIRED True)

    CMAKE_CXX_STANDARD:是用来设置 CXX_STANDARD 的宏

    CMAKE_CXX_STANDARD_REQUIRED:设置为True,则一定要使用 CMAKE_CXX_STANDARD 设置的标准;设置为False,则表示可选/请求,如果编译器不支持所请求的标准,则会降为之前的标准

  2. 在执行 cmake 命令的时候指定出这个宏的值

    1
    2
    3
    4
    5
    6
    #增加-std=c++11
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
    #增加-std=c++14
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
    #增加-std=c++17
    cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

1.2.3 指定输出的路径

在 CMake 中指定可执行程序输出的路径,也对应一个宏,叫做 EXECUTABLE_OUTPUT_PATH,它的值还是通过 set 命令进行设置:

1
2
set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)

如果这个路径中的子目录不存在,会自动生成,无需自己手动创建

1.3 搜索文件

使用 aux_source_directory 命令可以简单易用的搜索源文件;或者 file 命令可以指定更多参数来实现更强大的搜索。

1.3.1 aux_source_directory

使用 aux_source_directory 命令可以查找某个路径下所有的源文件,不能递归搜索,命令格式为:

1
aux_source_directory(< dir > < variable >)

例如:

1
2
# 搜索 src 目录下的源文件
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)

该命令搜索 ${PROJECT_SOURCE_DIR}/src 目录下的所有源文件,保存在变量 SRC_LTST 中。

宏 PROJECT_SOURCE_DIR 表示执行 cmake 命令后跟的目录,也就是 CMakeLists.txt 所在的目录,一般将CMakeLists.txt 放在项目根目录下。

1.3.2 file

使用 file 来搜索文件的格式:

1
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB:将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中
  • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中

例如:

1
2
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
  • CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。

  • 关于要搜索的文件路径和类型可加双引号,也可不加:

    1
    file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")

1.4 包含头文件

在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在 CMake 中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是 include_directories:

1
include_directories(headpath)

例如:

1
include_directories(${PROJECT_SOURCE_DIR}/include)

如果想要递归地包含某一目录下的所有头文件,可以采用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
macro(FIND_INCLUDE_DIR curdir)									        #定义函数,1个参数:指定路径curdir;
file(GLOB_RECURSE children "${curdir}/*.hpp" "${curdir}/*.h" ) #遍历获取{curdir}中*.hpp和*.h文件列表
set(dirlist "") #定义dirlist中间变量,并初始化
foreach(child ${children}) #for循环
string(REGEX REPLACE "(.*)/.*" "\\1" LIB_NAME ${child}) #字符串替换,用/前的字符替换/*h
if(IS_DIRECTORY ${LIB_NAME}) #判断是否为路径
list (FIND dirlist ${LIB_NAME} list_index) #去重,查找dirlist中是否有${LIB_NAME}指定的值,可以区分字符串相同数子后缀不同的路径:例如/app/test_1和/app/test_2
if(${list_index} LESS 0) #若没找到则代表列表中没有该路径
include_directories(${LIB_NAME})
endif() #结束判断
endif()
endforeach() #结束for循环
endmacro()

FIND_INCLUDE_DIR(${PROJECT_SOURCE_DIR}/include)

参考:链接

1.5 制作动态库/静态库

1.5.1 制作静态库

制作静态库/动态库的命令格式如下:

1
add_library(库名称 STATIC/SHARED 源文件1 [源文件2] ...) 

在 Linux 中静态库/动态库名称由 lib+库名称+.a 组成,所以在命令中仅需要指出库名称即可,其他部分会自动填写

例如制作一个静态库:

1
2
3
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

最终会生成静态库 libcalc.a

1.5.2 指定库的输出路径

方式1 - 适用于动态库

对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录

1
2
# 设置可执行程序/动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

方式2 - 都适用

由于在 Linux 下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH 宏了,而应该使用 LIBRARY_OUTPUT_PATH ,这个宏对应静态库文件和动态库文件都适用。

1
2
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

1.6 链接库文件

1.6.1 链接静态库

在cmake中,链接静态库的命令如下:

1
link_libraries(<static lib> [<static lib>...])
  • 参数1:指定出要链接的静态库的名字,可以是全名,也可以是掐头去尾的库名称
  • 参数2-N:要链接的其它静态库的名字

默认会去编译工具链的指定目录中搜索库,如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

1
link_directories(<lib path>)

使用 target_link_libraries 命令可以为某个 target 链接动态库,也可以链接静态库文件。

1.6.2 链接动态库

在cmake中链接动态库的命令如下:

1
2
3
4
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target:指定要加载动态库的文件的名字,谁要链接动态库

    • 该文件可能是一个源文件
    • 该文件可能是一个动态库文件
    • 该文件可能是一个可执行文件
  • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,**默认为 PUBLIC **

    • 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。

    • 动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法:

      1
      2
      target_link_libraries(A B C)
      target_link_libraries(D A)
    • PUBLIC:在 public 后面的库会被 Link 到前面的 target 中,并且里面的符号也会被导出,提供给第三方使用。

    • PRIVATE:在 private 后面的库仅被 link 到前面的 target 中,并且终结掉,第三方库不能感知你调了啥库

    • INTERFACE:在 interface 后面引入的库不会被链接到前面的 target 中,只会导出符号。假设一个 target link 多个以 INTERFACE 修饰的动态库,在该 target 并不能知道它所调用的符号是来自于哪一个动态库的

    • 从 PUBLIC|PRIVATE|INTERFACE,对被链接动态库的保护越来越强

(1) 链接动态库的时机

静态库会在生成可执行程序的链接阶段被打包到可执行程序中,链接静态库命令的调用应该在生成文件之前。

动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存。因此,在 cmake 中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后

1
2
3
4
5
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

这里 pthread 是系统提供的线程库,在链接时和运行时都能够在编译工具链的指定目录中找到

(2) 链接第三方库

在编译链接第三方库时,显然在工具链的指定目录中是找不到的,所以我们需要指定链接动态库的所在路径,和

指定静态库位置使用的是相同的命令:

1
link_directories(path)

完整的示例如下:

1
2
3
4
5
6
7
8
9
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app calc)

gcc 命令中可以通过-L参数指定链接时路径,通过-R/-rpath指定运行时路径; 在 CMake 中通过link_directories指定的动态库路径,实际上是同时设置了链接路径和运行路径为path所指定的绝对路径

假设我们将项目所要用到的动态库放在根目录下的 lib 目录中,在链接时没有问题,而想要最终编译出的可执行文件能够在任何地方都可以运行,需要考虑运行时应该去哪里寻找动态库:

  1. 比较简单的方案,将所依赖的动态库直接丢到存储动态库/静态库的系统目录中,如/lib//usr/lib/

  2. 为 target 单独指定运行路径,并将所依赖的动态库放到该指定运行路径中:

    设置 target 属性的命令格式如下:

    1
    2
    3
    set_target_properties(<targets> ...
    PROPERTIES <prop1> <value1>
    [<prop2> <value2>] ...)

    可以使用该命令来设置 target 的运行路径:

    1
    2
    # 设置目标的RPATH
    set_target_properties(Main PROPERTIES BUILD_RPATH "$ORIGIN/lib;/home/book/lib")

    通过 readelf 命令可以查看生成可执行文件的信息:

    1
    2
    3
    $ readelf -d ../bin/Main | grep 'RUNPATH'

    0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/lib:/home/book/lib:/home/book/vscodeFile/code/study_cmake/basic/build/../lib]

    可以看到 RUNPATH 包括我们设置的 RPATH 以及 link_directories 命令的路径

1.7 日志

在CMake中可以向用户显示一条消息,该命令的名字为message:

1
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
  • (无) :重要消息
  • STATUS :非重要消息
  • WARNING:CMake 警告, 会继续执行
  • AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
  • SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
  • FATAL_ERROR:CMake 错误, 终止所有处理过程

CMake 的命令行工具会在 stdout 上显示 STATUS 消息,在 stderr 上显示其他所有消息。CMake 的 GUI 会在它的 log 区域显示所有消息

例如:

1
2
3
4
5
6
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

1.8 变量操作

1.8.1 追加

有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过 file 命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用 set 命令也可以使用 list 命令。

(1) 使用 set 拼接

如果使用set进行字符串拼接,对应的命令格式如下:

1
set(变量名1 ${变量名1} ${变量名2} ...)

关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据,就会把原数据覆盖

例如:

1
2
3
4
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
set(SRC_1 ${SRC_1} ${SRC_2})

(2) 使用 list 拼接

对应的命令格式如下:

1
list(APPEND <list> [<element> ...])

list 命令的功能比 set 要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作, APPEND 表示进行数据追加,后边的参数和 set 就一样了

例如:

1
2
3
4
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
list(APPEND SRC_1 ${SRC_2})

使用这种方式进行拼接不会覆盖第一个参数中的原数据。

1.8.2 字符串移除

我们在通过 file 搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,可以使用 list 命令移除:

1
list(REMOVE_ITEM <list> <value> [<value> ...])

例如:

1
2
3
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)

1.8.3 list 命令的其他功能

  1. 获取 list 的长度

    1
    list(LENGTH <list> <output variable>)
    • LENGTH:子命令LENGTH用于读取列表长度
    • <list>:当前操作的列表
    • <output variable>:新创建的变量,用于存储列表的长度。
  2. 读取列表中指定索引的的元素,可以指定多个索引

    1
    list(GET <list> <element index> [<element index> ...] <output variable>)
    • <list>:当前操作的列表
    • <element index>:列表元素的索引
      • 从0开始编号,索引0的元素为列表中的第一个元素;
      • 索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
      • 当索引(不管是正还是负)超过列表的长度,运行会报错
    • <output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。
  3. 将列表中的元素用连接符(字符串)连接起来组成一个字符串

    1
    list (JOIN <list> <glue> <output variable>)
    • <list>:当前操作的列表
    • <glue>:指定的连接符(字符串)
    • <output variable>:新创建的变量,存储返回的字符串
  4. 查找列表是否存在指定的元素,若果未找到,返回 -1

    1
    list(FIND <list> <value> <output variable>)
    • <list>:当前操作的列表
    • <value>:需要再列表中搜索的元素
    • <output variable>:新创建的变量
      • 如果列表 <list> 中存在 <value>,那么返回 <value> 在列表中的索引
      • 如果未找到则返回 -1
  5. 将元素追加到列表中

    1
    list (APPEND <list> [<element> ...])
  6. 在list中指定的位置插入若干元素

    1
    list(INSERT <list> <element_index> <element> [<element> ...])
  7. 将元素插入到列表的0索引位置

    1
    list (PREPEND <list> [<element> ...])
  8. 将列表中最后元素移除

    1
    list (POP_BACK <list> [<out-var>...])
  9. 将列表中第一个元素移除

    1
    list (POP_FRONT <list> [<out-var>...])
  10. 将指定的元素从列表中移除

    1
    list (REMOVE_ITEM <list> <value> [<value> ...])
  11. 将指定索引的元素从列表中移除

    1
    list (REMOVE_AT <list> <index> [<index> ...])
  12. 移除列表中的重复元素

    1
    list (REMOVE_DUPLICATES <list>)
  13. 列表翻转

    1
    list(REVERSE <list>)
  14. 列表排序

    1
    list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
    • COMPARE:指定排序方法。有如下几种值可选:
      • STRING:按照字母顺序进行排序,为默认的排序方法
      • FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
      • NATURAL:使用自然数顺序排序
    • CASE:指明是否大小写敏感。有如下几种值可选:
      • SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
      • INSENSITIVE:按照大小写不敏感方式进行排序
    • ORDER:指明排序的顺序。有如下几种值可选:
      • ASCENDING:按照升序排列,为默认值
      • DESCENDING:按照降序排列

1.9 宏定义

在代码中通常会使用宏定义来条件编译某些代码,可以在 gcc/g++ 命令中来声明宏,例如:

1
$ gcc test.c -DDEBUG -o app

gcc/g++命令中通过参数-D 指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为DEBUG

CMake中我们也可以做类似的事情,对应的命令叫做add_definitions:

1
add_definitions(-D宏名称)

1.10 嵌套

如果项目很大,或者项目中有很多的源码目录,在通过 CMake 管理项目的时候如果只使用一个 CMakeLists.txt ,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt 文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。

1.10.1 嵌套节点关系

众所周知,Linux 的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:

  • 根节点CMakeLists.txt中的变量全局有效
  • 父节点CMakeLists.txt中的变量可以在子节点中使用
  • 子节点CMakeLists.txt中的变量只能在当前节点中使用

1.10.2 添加子目录

接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:

1
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:指定了 CMakeLists.txt 源文件和代码文件的位置,其实就是指定子目录
  • binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
  • EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的 ALL 目标里,并且也会被排除在 IDE工程文件之外。用户必须显式构建在子路径下的目标。

1.11 流程控制

在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。

1.11.1 条件判断

关于条件判断其语法格式如下:

1
2
3
4
5
6
7
if(<condition>)
<commands>
elseif(<condition>) # 可选快, 可以重复
<commands>
else() # 可选快
<commands>
endif()

(1) 基本表达式

1
if(<expression>)

如果是基本表达式,expression 有以下三种情况:常量、变量、字符串。

  • 如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
  • 如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False

(2) 逻辑判断

1
2
3
if(NOT <condition>)        # 取反
if(<cond1> AND <cond2>) # 与
if(<cond1> OR <cond2>) # 或

(3) 比较

基于数值的比较

1
2
3
4
5
if(<variable|string> LESS <variable|string>)          # 如果左侧数值小于右侧,返回True
if(<variable|string> GREATER <variable|string>) # 如果左侧数值大于右侧,返回True
if(<variable|string> EQUAL <variable|string>) # 如果左侧数值等于右侧,返回True
if(<variable|string> LESS_EQUAL <variable|string>) # LESS_EQUAL:如果左侧数值小于等于右侧,返回True
if(<variable|string> GREATER_EQUAL <variable|string>) # GREATER_EQUAL:如果左侧数值大于等于右侧,返回True

基于字符串的比较

1
2
3
4
5
if(<variable|string> STRLESS <variable|string>)
if(<variable|string> STRGREATER <variable|string>)
if(<variable|string> STREQUAL <variable|string>)
if(<variable|string> STRLESS_EQUAL <variable|string>)
if(<variable|string> STRGREATER_EQUAL <variable|string>)

(4) 文件操作

  1. 判断文件或者目录是否存在

    1
    if(EXISTS path-to-file-or-directory)

    如果文件或者目录存在返回True,否则返回False。

  2. 判断是不是目录

    1
    if(IS_DIRECTORY path)
    • 此处目录的 path 必须是绝对路径
    • 如果目录存在返回True,目录不存在返回False。
  3. 判断是不是软连接

    1
    if(IS_SYMLINK file-name)
    • 此处的 file-name 对应的路径必须是绝对路径
    • 如果软链接存在返回True,软链接不存在返回False。
  4. 判断是不是绝对路径

    1
    if(IS_ABSOLUTE path)
    • 关于绝对路径:
      • 如果是Linux,该路径需要从根目录开始描述
      • 如果是Windows,该路径需要从盘符开始描述
    • 如果是绝对路径返回True,如果不是绝对路径返回False。

(5) 其他

  1. 判断某个元素是否在列表中

    1
    if(<variable|string> IN_LIST <variable>)
    • CMake 版本要求:大于等于3.3
    • 如果这个元素在列表中返回True,否则返回False。
  2. 比较两个路径是否相等

    1
    if(<variable|string> PATH_EQUAL <variable|string>)
    • CMake 版本要求:大于等于3.24
    • 如果这个元素在列表中返回True,否则返回False。

1.12 循环

在 CMake 中循环有两种方式,分别是:foreach和while。

1.12.1 foreach

使用 foreach 进行循环,语法格式如下:

1
2
3
foreach(<loop_var> <items>)
<commands>
endforeach()

通过 foreach 我们就可以对 items 中的数据进行遍历,然后通过 loop_var 将遍历到的当前的值取出,在取值的时候有以下几种用法:

方法1

1
foreach(<loop_var> RANGE <stop>)
  • RANGE:关键字,表示要遍历范围
  • stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop
  • loop_var:存储每次循环取出的值

例如:

1
2
3
foreach(item RANGE 10)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()

方法2

1
foreach(<loop_var> RANGE <start> <stop> [<step>])
  • RANGE:关键字,表示要遍历范围
  • start:这是一个正整数,表示范围的起始值,也就是说最小值为 start
  • stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop
  • step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置
  • loop_var:存储每次循环取出的值

例如:

1
2
3
foreach(item RANGE 10 30 2)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()

方法3

1
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])

这是 foreach 的另一个变体,通过这种方式我们可以对更加复杂的数据进行遍历,前两种方式只适用于对某个正整数范围内的遍历。

  • IN:关键字,表示在 xxx 里边

  • LISTS:关键字,对应的是列表list,通过set、list可以获得

  • ITEMS:关键字,对应的也是列表

  • loop_var:存储每次循环取出的值

例如:

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.2)
project(test)
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,创建了两个 list 列表,在遍历的时候对它们两个都进行了遍历(可以根据实际需求选择同时遍历多个或者只遍历一个)。输出的日志信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

一共输出了7个字符串,说明遍历是没有问题的。接下来看另外一种方式:

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.2)
project(test)

set(WORD a b c "d e f")
set(NAME ace sabo luffy)
foreach(item IN ITEMS ${WORD} ${NAME})
message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,遍历过程中将关键字LISTS改成了ITEMS,后边跟的还是一个或者多个列表,只不过此时需要通过${}将列表中的值取出。其输出的信息和上一个例子是一样的

方法4

注意事项:这种循环方式要求CMake的版本大于等于 3.17

1
foreach(<loop_var>... IN ZIP_LISTS <lists>)

通过这种方式,遍历的还是一个或多个列表,可以理解为是方式3的加强版。

  • loop_var:存储每次循环取出的值,可以根据要遍历的列表的数量指定多个变量,用于存储对应的列表当前取出的那个值。
    • 如果指定了多个变量名,它们的数量应该和列表的数量相等
    • 如果只给出了一个 loop_var,那么它将一系列的 loop_var_N 变量来存储对应列表中的当前项,也就是说 loop_var_0 对应第一个列表,loop_var_1 对应第二个列表,以此类推……
    • 如果遍历的多个列表中一个列表较短,当它遍历完成之后将不会再参与后续的遍历(因为其它列表还没有遍历完)。
  • IN:关键字,表示在 xxx 里边
  • ZIP_LISTS:关键字,对应的是列表list,通过set 、list可以获得
1
2
3
4
5
6
7
8
9
10
11
12
13
# 通过list给列表添加数据
list(APPEND WORD hello world "hello world")
list(APPEND NAME ace sabo luffy zoro sanji)
# 遍历列表
foreach(item0 item1 IN ZIP_LISTS WORD NAME)
message(STATUS "当前遍历的值为: item1 = ${item0}, item2=${item1}" )
endforeach()

message("=============================")
# 遍历列表
foreach(item IN ZIP_LISTS WORD NAME)
message(STATUS "当前遍历的值为: item1 = ${item_0}, item2=${item_1}" )
endforeach()

上面的例子输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cd build/
$ cmake ..
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
=============================
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build

1.12.2 while

除了使用foreach也可以使用 while 进行循环,关于循环结束对应的条件判断的书写格式和if/elseif 是一样的。while的语法格式如下:

1
2
3
while(<condition>)
<commands>
endwhile()

while循环比较简单,只需要指定出循环结束的条件即可,例如:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER 0)
message(STATUS "names = ${NAME}")
# 弹出列表头部元素
list(POP_FRONT NAME)
# 更新列表长度
list(LENGTH NAME LEN)
endwhile()

输出的结果如下:

1
2
3
4
5
6
7
8
9
10
$ cd build/
$ cmake ..
-- names = luffy;sanji;zoro;nami;robin
-- names = sanji;zoro;nami;robin
-- names = zoro;nami;robin
-- names = nami;robin
-- names = robin
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build

1.13 CMake 常用预定义宏:

功能
PROJECT_SOURCE_DIR 使用cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR 执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIR target 编译目录
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
PROJECT_NAME 返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR 项目实际构建路径,假设在 build 目录进行的构建,那么得到的就是这个目录的路径

1.14 CMake配置交叉编译

参考:[链接](【CMake 入门与进阶(13)】 CMake如何设置交叉编译(附代码)_cmake指定交叉编译工具-CSDN博客)、[链接](CMake及交叉工具编译链的安装使用_cmake 需要编译链编译吗-CSDN博客)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.10)

set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字
set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构

# 指定编译器的 sysroot 路径
set(TOOLCHAIN_DIR /home/book/100ask_imx6ull_mini-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/arm-buildroot-linux-gnueabihf/sysroot)

# 指定交叉编译器 arm-linux-gcc 和 arm-linux-g++
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/arm-buildroot-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/arm-buildroot-linux-gnueabihf-g++)


project(TEST_ONE)

2.Clangd

在项目根目录下新建.vscode目录,在该目录下新建settings.json文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
// 指定 cmake 构建路径
"cmake.buildDirectory": "${workspaceFolder}/build",
// 调用 cmake 命令构建时生成 compile_commands.json
"cmake.buildEnvironment": {"CMAKE_EXPORT_COMPILE_COMMANDS": "NO"},

"clangd.fallbackFlags": [ // 添加头文件路径
"-I/home/book/vscodeFile/code/study_clangd/mulit_file/include"
],

"clangd.arguments": [
"--background-index", // 启用后台索引,提供更快的代码导航和补全
"--all-scopes-completion", // 启用在所有作用域中进行代码补全
"--completion-style=detailed", // 设置代码补全的样式为详细模式
"--compile-commands-dir=${workspaceFolder}/build", // 指出compile_commands.json的所在位置
"--query-driver=/home/book/100ask_imx6ull_mini-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-buildroot-linux-gnueabihf-gcc"
// 配置编译器路径
],
}

配置 Clangd 正确索引交叉编译工具链参考:[链接](vscode/vim+clangd 环境中实现正确索引交叉编译链中系统头文件路径_.clangd-CSDN博客)

配置 vscode 下 Clangd 的使用:[链接](VSCode 配置 C++:VSCode + Clang + Clangd + LLDB + CMake + Git_vscode clang-CSDN博客)