关于CMake的一些用法

关于CMake的一些用法

以下纪录了我使用CMake期间遇到的各种问题和基本函数用法

1 在VScode中使用CMake

安装CMake

下载链接:Download CMake

尽量选择Latest Release版本,比较稳定。
如图中红框所示,下载后缀为.msi的安装文件,然后直接安装。

验证安装成功:cmake --version

安装MinGW

VScode的C++环境配置 - Blog of Mr.Juan (ljy0109.github.io)

VSCode中配置CMake

选择编译工具链

使用CMake编译

2 make命令无法识别

make : 无法将“make”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包 括路径,请确保路径正确,然后再试一次。

这是因为make命令实际上是调用mingw64/bin文件夹中的mingw32-make.exe文件。无法识别的原因是命令必须与文件名一致。

解决方法:复制一份mingw32-make.exe文件,并重命名为make.exe

3 eigen3/Eigen/Core: No such file or directory

下载链接:Eigen

1
2
3
4
fatal error: eigen3/Eigen/Core: 
No such file or directory
2 | #include<eigen3/Eigen/Core>
| ^~~~~~~~~~~~~~~~~~~

这个问题出在项目的CMakeLists.txt文件编写中。具体原因是包含eigen3库的路径有问题。

eigen3没有库,只有头文件,所以cmake只需要include头文件的路径就好了。

错误示范

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.5.0)
project(Transformation)

# 设置 Eigen3 库的路径
set(EIGEN3_INCLUDE_DIR "E:/Code/VSCode/library/eigen3")

# 添加 Eigen3 库的头文件目录到项目的头文件搜索路径中
include_directories(EIGEN3_INCLUDE_DIR)
# 添加项目的源文件
add_executable(Transformation main.cpp)

正确示范

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.5.0)
project(Transformation)

# 设置 Eigen3 库的路径
set(EIGEN3_INCLUDE_DIR "E:/Code/VSCode/library")

# 添加 Eigen3 库的头文件目录到项目的头文件搜索路径中
include_directories(${EIGEN3_INCLUDE_DIR})
# 添加项目的源文件
add_executable(Transformation main.cpp)

上述错误示范中有两处错误:

  • 路径错误。观测.cpp文件中include头文件的路径:#include<eigen3/Eigen/Core>。所以在CMakeLists.txt文件包含库时,不需要精确到目标库所在的那个文件夹。

    • 举个例子:eigen3库所在路径E:/Code/VSCode/library/eigen3,文件夹eigen3存放eigen3库,文件夹eigen3在文件夹library内。那么在CMakeLists.txt包含时,只用指定到文件夹library就行了。#include<eigen3/Eigen/Core>命令会接着文件夹library向后搜索
  • include_directories()错误。在 CMake 中,include_directories() 函数用于将指定的目录添加到项目的头文件搜索路径中。但是,include_directories() 函数需要传递一个有效的目录路径作为参数。在这种情况下,EIGEN3_INCLUDE_DIR 是一个变量,存储了 Eigen3 库的头文件目录路径。

    现在来看一下这两种用法的区别:

    1. include_directories(${EIGEN3_INCLUDE_DIR}):这是正确的用法。${EIGEN3_INCLUDE_DIR} 是一个变量,用于存储 Eigen3 库的头文件目录路径。通过使用 ${EIGEN3_INCLUDE_DIR},可以将该变量的值(即 Eigen3 库的头文件目录路径)传递给 include_directories() 函数,从而将 Eigen3 库的头文件目录添加到项目的头文件搜索路径中。
    2. include_directories(EIGEN3_INCLUDE_DIR):这是错误的用法。在这种情况下,EIGEN3_INCLUDE_DIR 并不是一个变量,而是一个字符串。因此,传递给 include_directories() 函数的参数实际上是一个字符串 "EIGEN3_INCLUDE_DIR",而不是变量 EIGEN3_INCLUDE_DIR 的值。这样做会导致编译器无法正确解析路径,因为它实际上会在搜索路径中添加一个名为 "EIGEN3_INCLUDE_DIR" 的字符串,而不是指定的实际路径。

    因此,正确的做法是使用 ${EIGEN3_INCLUDE_DIR},而不是直接使用字符串 "EIGEN3_INCLUDE_DIR"。这样可以确保将变量的值传递给 include_directories() 函数,从而正确地将 Eigen3 库的头文件目录添加到项目的头文件搜索路径中。

4 在VScode中调试CMake的程序

vscode下cmake工程环境配置以及调试配置(c++)_vscode cmake 调试-CSDN博客

在VScode中的运行与调试区域点击链接生成launch.json文件。

将生成的launch.json文件按照下列代码更改即可进行程序调试。需要安装cmake Tool插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"version": "0.2.0",
"configurations": [
{
"name": "g++.exe",
"type": "cppdbg",
"request": "launch",
//"program": "${workspaceFolder}/build/${fileBasenameNoExtension}.exe",//${fileBasenameNoExtension}
"program": "${command:cmake.launchTargetPath}", //${fileBasenameNoExtension}
//如果用上面这条program的设置,可以一键调试,需要注释preLaunchTask同时可以不需要编写tasks.json,需要安装cmake Tool插件
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,//是否有黑框框
"MIMode": "gdb",
"miDebuggerPath": "F:/MinGW64/bin/gdb.exe", //调试器地址
//"preLaunchTask": "Build" //预编译,调用tasks.json,与tasks.json里面的label一致
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]

}
]
}

5 undefined reference to 'omp_get_thread_num'

undefined reference to omp_get_thread_num'_undefined reference toomp_set_num_threads-CSDN博客

undefined reference to ‘omp_set_num_threads’ - CSDN文库

错误表示:编译器无法找到OpenMP库中的函数 omp_get_thread_num

需要修改CMakeLists.txt 文件,增加:

1
2
3
4
5
6
FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
message("OPENMP FOUND")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()

基本函数用法

CMakeLists.txt的基本组成部分

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.10) # cmake最低版本要求
project(Rasterizer) # 项目名称

find_package(OpenCV REQUIRED) # 查找并加载特点库

set(CMAKE_CXX_STANDARD 17) # 变量赋值

include_directories(/usr/local/include) # 添加包含目录

add_executable(Rasterizer main.cpp rasterizer.hpp rasterizer.cpp Triangle.hpp Triangle.cpp) # 生成执行文件
target_link_libraries(Rasterizer ${OpenCV_LIBRARIES}) # 链接库

set()

set() 是 CMake 中用于设置变量的命令。它可以用于创建新变量、修改已存在变量的值,以及执行其他与变量相关的操作。

基本语法如下:

1
set(variable value)

其中 variable 是要设置的变量的名称,value 是要给变量赋予的值。value 可以是一个字符串、一个列表,也可以是其他 CMake 支持的数据类型。

示例:

1
set(MY_VARIABLE "Hello, world!")

这行代码将一个名为 MY_VARIABLE 的变量设置为字符串 "Hello, world!"

如果要设置的值包含空格或其他特殊字符,可以将值放在引号中以避免意外解析错误。

另外,set() 命令还可以用于执行其他操作,例如向列表变量添加元素、从环境变量中获取值等。以下是一些示例:

1
2
set(SOURCES main.cpp functions.cpp)  # 创建列表变量
set(ENV_VAR $ENV{MY_ENVIRONMENT_VARIABLE}) # 从环境变量中获取值

在 CMake 中,变量的作用域是全局的,因此可以在项目的任何位置访问和修改变量。但是,建议将变量的作用域限制在最小范围内,以避免潜在的命名冲突和混乱。

find_package()

find_package() 是 CMake 中用于查找并加载特定软件包的模块的命令。它用于在系统中查找指定软件包的安装位置,并将其包含路径、库文件路径等信息导入到 CMake 构建系统中,以便于后续的项目构建和链接。

基本语法如下:

1
2
3
4
find_package(package_name [version] [EXACT] [QUIET] [REQUIRED]
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])

其中:

  • package_name 是要查找的软件包的名称。
  • version 是可选的,用于指定软件包的版本。
  • EXACT 表示要求查找到的软件包版本必须与指定的版本完全匹配。
  • QUIET 表示即使未找到软件包也不会产生错误。
  • REQUIRED 表示必须找到指定的软件包,否则将产生错误。
  • COMPONENTS 用于指定要查找的特定组件或模块。
  • OPTIONAL_COMPONENTS 用于指定可选的组件。
  • NO_POLICY_SCOPE 表示在查找软件包时不要修改全局策略。

示例:

1
find_package(OpenCV REQUIRED)

这行代码用于查找并加载 OpenCV 软件包,并将其相关信息导入到 CMake 构建系统中。在这个示例中,REQUIRED 表示必须找到 OpenCV 软件包,否则会产生错误。

find_package() 命令通常与 include_directories()target_link_libraries() 等命令一起使用,以便于在项目中使用找到的软件包。例如:

1
2
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(my_program ${OpenCV_LIBS})

这样可以将 OpenCV 软件包的包含路径和库文件链接到项目中。

include_directories()

include_directories 是 CMake 中用于添加包含目录的命令。它用于将指定目录添加到编译器的包含路径中,以便编译器可以找到并包含这些目录中的头文件。

基本语法如下:

1
include_directories(directory1 directory2 ...)

其中 directory1 directory2 ... 是您要添加到包含路径的目录列表。

示例:

1
2
include_directories(include)
include_directories(src)

这些命令将 includesrc 目录添加到编译器的包含路径中。这样,在编译时,编译器就可以在这些目录中找到头文件,并将它们包含到源代码中。

需要注意的是,include_directories 命令会将指定的目录添加到所有目标的包含路径中,包括通过 add_executableadd_library 定义的可执行文件和库文件。如果您只想为特定目标添加包含路径,可以使用 target_include_directories 命令。

引用变量${}

在 CMake 中,${} 是用于引用变量的语法。它允许您在 CMake 脚本中引用变量的值,并将其插入到代码中。

基本用法如下:

1
${variable_name}

其中 variable_name 是您要引用的变量的名称。${} 语法将被替换为变量的实际值。

示例:

1
2
set(MY_VARIABLE "Hello, world!")
message("${MY_VARIABLE}")

在这个示例中,${MY_VARIABLE} 被替换为变量 MY_VARIABLE 的值,即 "Hello, world!"message() 函数会将该值打印到 CMake 输出中。

${} 语法还允许进行一些高级操作,例如字符串连接、数学运算、路径操作等。以下是一些常见的用法:

  • 字符串连接:

    1
    2
    3
    set(STRING1 "Hello")
    set(STRING2 "world!")
    message("${STRING1}, ${STRING2}")
  • 数学运算:

    1
    2
    3
    4
    set(NUMBER1 10)
    set(NUMBER2 20)
    math(EXPR RESULT "${NUMBER1} + ${NUMBER2}")
    message("The result is ${RESULT}")
  • 路径操作:

    1
    2
    3
    set(PATH "/usr/local/include")
    get_filename_component(DIRECTORY_NAME "${PATH}" DIRECTORY)
    message("The directory name is ${DIRECTORY_NAME}")

add_executable()

add_executable 是 CMake 中用于定义可执行文件的命令。它的作用是将源文件编译成一个可执行文件。

基本语法如下:

1
add_executable(target_name source1.cpp source2.cpp ...)

其中:

  • target_name 是要生成的可执行文件的名称。
  • source1.cpp source2.cpp ... 是要编译的源文件列表。

示例:

1
add_executable(my_program main.cpp functions.cpp)

这行命令会将 main.cppfunctions.cpp 这两个源文件编译成一个名为 my_program 的可执行文件。

还可以使用变量来指定源文件列表,例如:

1
2
set(SOURCES main.cpp functions.cpp)
add_executable(my_program ${SOURCES})

target_link_libraries 是 CMake 中用于将目标(例如可执行文件或库)与特定的库文件链接起来的命令。它的作用是将指定的库文件与目标进行链接,使得目标可以使用这些库中提供的函数和功能。

基本语法如下:

1
target_link_libraries(target_name library1 library2 ...)

其中:

  • target_name 是要链接库的目标的名称,通常是通过 add_executableadd_library 命令定义的。
  • library1 library2 ... 是要链接的库文件的名称,可以是库文件的名称(例如 mylib)或者是完整的路径(例如 /path/to/mylib.lib)。

示例:

1
target_link_libraries(my_program my_lib)

这行命令会将名为 my_lib 的库文件链接到 my_program 可执行文件中。

可以链接多个库文件,例如:

1
target_link_libraries(my_program my_lib1 my_lib2)

这样可以将多个库文件链接到同一个目标中。

另外,还可以链接系统提供的库,例如:

1
target_link_libraries(my_program pthread)

这行命令会链接 POSIX 线程库(pthread)到 my_program 可执行文件中,使得 my_program 可以使用 POSIX 线程库中提供的函数和功能。

动态链接与静态链接

target_link_libraries 可以用于进行静态链接或动态链接,具体取决于所链接的库文件是静态库还是动态库。

  • 静态链接: 如果链接的是静态库文件(.a.lib),则会进行静态链接。在静态链接时,库文件的内容会被复制到最终生成的可执行文件中,因此可执行文件独立于外部库文件。
  • 动态链接: 如果链接的是动态库文件(.so.dll),则会进行动态链接。在动态链接时,可执行文件会保留对动态库的引用,而实际的库文件会在运行时加载到内存中。因此,动态链接库文件通常不会包含在最终的可执行文件中。

默认情况下,CMake 会自动根据库文件的后缀来判断是静态链接还是动态链接。例如,.a.lib 后缀的库文件通常被视为静态库,.so.dll 后缀的库文件通常被视为动态库。也可以通过显式指定 STATICSHARED 参数来指定链接类型,例如:

1
2
target_link_libraries(my_program PRIVATE my_static_lib)  # 静态链接
target_link_libraries(my_program PRIVATE my_shared_lib) # 动态链接

在上述示例中,PRIVATE 关键字用于指定链接的范围,my_static_libmy_shared_lib 分别是静态库和动态库的名称。


关于CMake的一些用法
http://example.com/2024/03/06/关于CMake的一些用法/
作者
Mr.Yuan
发布于
2024年3月6日
许可协议