CMakeLists 学习指南

本文最后更新于:2024年2月24日 下午

CMake是一个元(meta)构建系统,可用于为许多其他构建工具创建构建文件。
三天速通!结合UGAS的CMakeLists食用更香捏!

上交教程:

https://sjtu-robomaster-team.github.io/vision-learning-4-cmake-introduction/

完整教程:

https://zhuanlan.zhihu.com/p/367808125

核心语法:

https://zhuanlan.zhihu.com/p/368701263

使用Cmake+VScode编译构建C++文件

  • 编写好CMakeLists.txt
  • 执行Cmake的confit命令。直接调用命令台工具(Ctrl + Shift + P),然后选择Cmake Config
  • 点击下面的build按钮,编译成功。
    • 所有的编译后的东西自动被vscode的cmake插件放入了build文件夹中,这个文件夹也是cmake插件自动生成的。
  • 运行生成即可run你的工程文件。

速览:处理多源文件目录的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cmake_minimum_required(VERSION 3.5)

project (hello_headers)
#设置了项目的名字为 hello_headers

set(SOURCES
src/Hello.cpp
src/main.cpp
)
#set 指令创建了一个变量,第一个参数为变量名 `SOURCES`,后面的参数就是这个变量所指代的内容

add_executable(hello_headers ${SOURCES})
# ${SOURCES} 使用前面创建的变量来代替要进行编译的文件。
# hello_headers 指明了生成可执行文件需要的资源文件
#PRIVATE 关键字。

target_include_directories(hello_headers
PRIVATE
${PROJECT_SOURCE_DIR}/include
)
#添加编译需要用到的头文件,hello_headers指明了需要的头文件的目录
#${PROJECT_SOURCE_DIR}指明include文件所在的文件夹路径。(include文件夹所在的目录。)
  • 关键字有三种,PRIVATE | PUBLIC | INTERFACE,不同的关键字在进行CMake编译后会生成不同的include 文件夹
  • 在CMake中,变量的使用都是 ${变量名} 这种格式。
  • 除了README.md文件是用来讲解的文件外,另外几个文件都是要进行编译的。
  • ${PROJECT_SOURCE_DIR}指当前项目的顶级(上级)源目录,则在编写程序时引用include里面的头文件可以直接写头文件名,不用写相对路径了
  • ${CMAKE_CXX_FLAGS} C++编译器的编译选项。具体常用选项有:

编译指令:

1
2
3
4
5
6
7
mkdir build

cd build

cmake ..

make

编译完成后,在终端输入:

1
./hello_headers

就可以运行程序文件。

高级点的语法:

递归搜索所有的.cpp文件并将列表存储在一个变量中:

1
2
3
file(GLOB_RECURSE UGAS_SOURCE CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/src/*.cpp
${PROJECT_SOURCE_DIR}/src/*.cc)
  • CONFIGURE_DEPENDS
    告知 CMake 有关配置过程的其他输入文件的信息。如果修改了任何命名文件,生成系统将重新运行 CMake 以重新配置文件并再次生成生成系统。
    将文件指定为以分号分隔的路径列表。
  • configure_file:通过读取输入文件中的内容,将 CMakeLists.txt 文件中的变量转变为 C/C++ 中可识别的宏定义,然后存入输出文件中。
    1
    configure_file(<input> <output>)
    具体参考:https://zhuanlan.zhihu.com/p/436923370

输入文件为 xxx(目录路径)-config.h.in
输出文件为 xxx-config.h

选项开关:

1
option(<OPTION_NAME> "<help_text>" [value])

第一个参数为选项名称。此选项不仅可以是boolean,也可以是string或list。

第二个参数为选项介绍,为string。

第三个参数为选项默认值,依据选项类型设置。

  • 条件分支:
    1
    2
    3
    4
    5
    6
    7
    if (CONDITION_1)
    # do something
    elseif (CONDITION_2)
    # do something
    else()
    # do something
    endif()

find_package

查找并载入一个外部包

1
2
3
4
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])

最常用用法:
find_package(OpenCV 4.7 REQUIRED)
find_package(HikCameraSDK REQUIRED)
查找名为 XX 的包,找不到就报错(并终止 cmake 进程,不再继续往下执行)。

  • include_directories
    cmake使用 include_directories是用来 提供搜索头文件路径
1
include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

在ROS2环境下编译:

  • 生成目标文件,并且不需要再使用target_link_libraries
    1
    2
    3
    ament_auto_add_executable

    ament_auto_add_library
  • ament_auto_find_build_dependencies
    ROS2环境下的链接库方式,相当于不用写find_package了。(ROS之前连接库的指令)
  • ament_auto_package
    这个用来代替以前的export(导出库)和两次install(安装库),以及最后的ament_package
  • link_directories
    链接库;添加需要链接的库文件路径之后就可以使用相对路径,使用TARGET_LINK_LIBRARIES时,只需给出动态链接库名即可。
  • target_link_libraries
    • 如果所有目标都使用相关的include目录,则需要使用link_libraries;
      如果路径是特定于目标,就用target_link_libraries
    • 如果所有目标都使用相关的include目录,则需要使用link_libraries;
      如果路径是特定于目标,就用target_link_libraries
  1. DEBUG时使用的命令:
  • 打印变量信息

    • SET(USER_KEY, “Hello World”)\

#设置变量

  • MESSAGE( STATUS “this var key = ${USER_KEY}.”)
  • message([] “message text” …)

UGAS的CMakeLists.txt参考:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
cmake_minimum_required(VERSION 3.12)
project(ugas VERSION 1.0 LANGUAGES C CXX)

# Set compilation flags
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)

# Set configure_file
set (CONFIGURE_DIR_PATH ${PROJECT_SOURCE_DIR}/build/config)
configure_file (
"${PROJECT_SOURCE_DIR}/src/config.h.in"
"${CONFIGURE_DIR_PATH}/config.h")

# Initialize custom options
option (ENABLE_DEBUG_CANVAS "Enable debug canvas to draw debug image" OFF)
option (ENABLE_RECORDING "Enable recording of raw camera image" OFF)
option (ENABLE_OPENVINO "Enable openvino to identify buff" OFF)
option (ENABLE_ROS "Enable ROS to visualize positions" OFF)

# Set the output executable file name:
# When compiling in the ROS environment, the output executable file name will be the node name.
# Otherwise, the output file name will be the project name.
if (ENABLE_ROS)
set(EXECUTABLE_NAME main)
else ()
set(EXECUTABLE_NAME ${PROJECT_NAME})
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build)
endif()

# Find non-ros packages
find_package(OpenCV 4.7 REQUIRED)
find_package(HikCameraSDK REQUIRED)

if (ENABLE_OPENVINO)
find_package(OpenVINO REQUIRED)
set(OpenVINO_LIB openvino::runtime)
endif (ENABLE_OPENVINO)

# Include project source directory
include_directories(${PROJECT_SOURCE_DIR}/src ${CONFIGURE_DIR_PATH})

# Recursively search for all source files under the 'src' folder and store them into UGAS_SOURCE variable
# Flag 'CONFIGURE_DEPENDS' asks cmake to detect GLOB result changes so no need to rerun cmake when adding a new source file.
file(GLOB_RECURSE UGAS_SOURCE CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/src/*.cpp
${PROJECT_SOURCE_DIR}/src/*.cc)

# Find ros packages & add source files to compilation
if (ENABLE_ROS)
find_package (ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies ()
ament_auto_add_executable(${EXECUTABLE_NAME} ${UGAS_SOURCE})
else()
add_executable(${EXECUTABLE_NAME} ${UGAS_SOURCE})
endif()

# Link libraries
target_link_libraries(${EXECUTABLE_NAME} ${OpenCV_LIBS} ${HikCameraSDK_LIB} ${OpenVINO_LIB} -lpthread)

# Install package
if (ENABLE_ROS)
ament_auto_package()
endif()

使用ninja构建

概念——生成器

CMake生成器负责为底层构建系统编写输入文件(例如Makefile)。
运行cmake--help将显示可用的生成器。
CMake包括不同类型的生成器,如命令行生成器、IDE生成器和其他生成器。

  1. 命令行生成工具生成器
    这些生成器用于命令行构建工具,如Make和Ninja。
  2. IDE构建工具生成器
    这些生成器用于集成开发环境,其中包括它们自己的编译器。例如Visual Studio和Xcode,它们本身就包含一个编译器。
  3. 其他生成器
    这些生成器创建配置并与其他IDE工具共同工作,并且必须包含在IDE或命令行生成器中。
Note 在本例中,ninja是通过命令sudo apt-get install ninja-build安装的。

调用生成器

1
2
3
4
$ cmake .. -G Ninja

$ ls
build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake rules.ninja

CMake将生成所需的Ninja构建文件,这些文件可以通过使用Ninja命令运行。

实例:使用Ninja编译UGAS

1
2
3
4
5
mkdir -p ~/ros2_ws/src & cd ~/ros2_ws                   #创建工作区文件夹
cp -r ~/Desktop/UGAS ./src/ugas #拷贝目录
colcon build --packages-select ugas --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_ROS=ON -GNinja
source ./install/local_setup.bash #配置该功能包环境
ros2 run ugas main #运行一个节点

tips:

  • colcon:使用colcon构建ros包,相当于ros1中的catkin工具
  • cp -r Dir/ /home/test :将Dir目录copy到test目录下

CMakeLists 学习指南
http://zoechen04616.github.io/2023/07/25/CMakeLists-学习指南/
作者
Yunru Chen
发布于
2023年7月25日
许可协议