最近在进行c++代码的编译,发现cmakelist文件中有很多语句。一开始小白并不理解什么含义,学习后特此记录,以方便以后自己查阅。
情况一:单个模块,无主模块包含多个子模块的情况
文件目录如下:
MyProject/ ├── CMakeLists.txt ├── include/ │ └── my_library.h ├── src/ │ ├── main.cpp │ └── my_library.cpp └── bin/
指定最低 CMake 版本 |
cmake_minimum_required(VERSION 3.10) |
模块名称 |
project(MyProject) |
设置头文件目录
|
target_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 将当前源代码目录下的 include 文件夹添加到头文件搜索路径中,编译时可以正确找到 my_library.h |
添加目标(可执行文件或库) |
add_executable(my_executable main.cpp) (生成一个名为 my_executable 的可执行文件,源文件为 main.cpp) |
add_library(my_library STATIC my_library.cpp) (创建一个名为 my_library 的静态库,源文件为 my_library.cpp) |
|
路径说明 (可选) |
set(OpenCV_DIR "/usr/local/include/opencv4/") OpenCV_DIR:这是一个变量名,通常用于指定 OpenCV 的安装路径。 "/usr/local/include/opencv4/":这是变量的值,表示 OpenCV 的头文件路径。 |
查找库 (可选) |
find_package(OpenCV REQUIRED) OpenCV:这是需要查找的库的名称。 REQUIRED:表示如果找不到 OpenCV,CMake 会报错并停止配置过程。 说明: OpenCV 的库文件(如 .so 或 .a 文件)通常安装在 /usr/local/lib/ 目录下,而不是头文件路径 /usr/local/include/opencv4/
当使用 find_package(OpenCV REQUIRED) 命令时,CMake 会根据 OpenCV_DIR 提供的路径来查找 OpenCV 的头文件。同时,它会自动在标准路径(如 /usr/local/lib/)中查找 OpenCV 的库文件。
虽然 OpenCV_DIR 指向的是头文件路径,但 find_package() 会根据这个路径推断出库文件的可能位置(通常是 /usr/local/lib/)。如果在这个位置没有找到库文件,find_package() 会继续在其他标准路径中查找,以确保能够正确找到 OpenCV 的头文件和库文件)
找到后会自动配置变量: OpenCV_INCLUDE_DIRS:包含OpenCV头文件的路径。 OpenCV_LIBRARIES:包含OpenCV库文件的路径 获取到的OpenCV_INCLUDE_DIRS、OpenCV_LIBRARIES路径可以在当前目录和子目录中使用。 然后就可以继续配置opencv的头文件和库文件了 target_include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(my_executable ${OpenCV_LIBRARIES}) |
链接库
|
target_link_libraries(my_executable my_library) 将 my_library 链接到 my_executable。这样 my_executable 在运行时可以使用 my_library 中定义的函数。 |
设置输出目录 |
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) 将可执行文件的输出目录设置为 bin/,可在该目录下找到可执行文件 |
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) 将库文件的输出目录也设置为 bin/,可在该目录下找到动态/静态链接库 |
情况二:多个模块,主模块包含多个子模块的情况
文件目录如下:
MyMainProject/ ├── CMakeLists.txt # 主模块的 CMakeLists.txt ├── main.cpp # 主模块的源文件 ├── subproject1/ │ ├── CMakeLists.txt # 子模块1的 CMakeLists.txt │ ├── src/ │ │ └── subproject1.cpp # 子模块1的源文件 │ └── include/ │ └── subproject1.h # 子模块1的头文件 ├── subproject2/ │ ├── CMakeLists.txt # 子模块2的 CMakeLists.txt │ ├── src/ │ │ └── subproject2.cpp # 子模块2的源文件 │ └── include/ │ └── subproject2.h # 子模块2的头文件
现在的cmakelist文件分为两种,一种是主模块的cmakelist文件,一种是子模块的cmakelist文件。
先说主模块的cmakelist(主要把各个子模块链接起来)
指定最低 CMake 版本 |
cmake_minimum_required(VERSION 3.10) |
主项目名称 |
project(MyMainProject) |
添加子项目 |
add_subdirectory(subproject1) add_subdirectory(subproject2) 将子项目 subproject1 和 subproject2 纳入主项目的构建体系中。CMake 会递归解析这些目录下的 CMakeLists.txt 文件。
注意:可能也会存在以下写法 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/ 3rdparty.out) 将 3rdparty 子目录纳入构建体系,输出目录为 3rdparty.out。 3rdparty.out 是相对于当前构建目录(build)的输出目录。 在构建过程中,3rdparty 子项目的输出将被放置在这些目录中。
|
定义主项目的可执行文件 |
add_executable(main_app src/main.cpp) 定义主项目的可执行文件 main_app,源文件为 src/main.cpp |
链接子项目生成的库
|
target_link_libraries(main_app subproject1_lib subproject2_lib) 将子项目生成的库 subproject1_lib 和 subproject2_lib 链接到主项目的可执行文件 main_app |
链接子项目的头文件
|
target_include_directories(main PUBLIC ${CMAKE_SOURCE_DIR}/subproject1/include ${CMAKE_SOURCE_DIR}/subproject2/include) 把这两个 include/ 目录挂到可执行文件上 |
设置输出目录
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) 将可执行文件的输出目录设置为 bin/ |
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) 将库文件的输出目录设置为 bin/ |
如果不在主模块中target_include_directories(引入头文件搜索路径),那么必须在子模块中定义好此部分,以subproject2为例。
target_include_directories(subproject2 PUBLIC subproject2/include)
因为PUBLIC 关键字把 头文件搜索路径 登记到了这两个 库目标 的 INTERFACE_INCLUDE_DIRECTORIES 属性上。CMake 就会自动把这两条 INTERFACE 路径 追加 到 main 的编译 flags 里,无需再写一次 target_include_directories。
谨记:
如果只写 target_include_directories,编译能通过,链接会报 undefined reference;
如果只写 target_link_libraries,链接能找到库,但编译会报 找不到头文件;
因此 两个命令都必须出现,只是它们不一定都由“你”亲自写——现代 CMake 的传递性(PUBLIC)可以帮你省掉其中一段,但底层信息仍然缺一不可。
再说子模块的cmakelist(指定源文件,并管理自己“一亩三分地”的内部依赖)
子项目名称 |
project(Subproject1) |
设置子项目头文件目录 |
target_include_directories(subproject1 PUBLIC subproject1/include) |
定义子项目的目标,生成一个静态链接库 |
add_library(subproject1_lib STATIC subproject1/src/subproject1.cpp) |
子项目的cmakelist向主项目cmakelist传递路径(可选) |
set(JPEG_PATH ${CMAKE_CURRENT_SOURCE_DIR}/jpeg_turbo) 设置JPEG_PATH为子项目目录下的jpeg_turbo文件夹路径
set(LIBJPEG ${JPEG_PATH}/Linux/${TARGET_LIB_ARCH}/libturbojpeg.a PARENT_SCOPE) 使用PARENT_SCOPE将LIBJPEG(链接库)传递到上一级的CMakeLists.txt 文件中。
set(LIBJPEG_INCLUDES ${JPEG_PATH}/include PARENT_SCOPE) 使用PARENT_SCOPE将LIBJPEG_INCLUDES(头文件)传递到上一级的CMakeLists.txt 文件中。
如果在 3rd 文件夹下的 CMakeLists.txt 文件中设置了变量 LIBJPEG_INCLUDES 并使用了 PARENT_SCOPE,那么在与 3rd 同级的 cpp 文件夹下的 CMakeLists.txt 文件中也可以直接使用这个变量。 |
向生成的静态库添加头文件(比较绕) |
set(SOURCES rknpu1/yolov8_seg.cc rknpu1/postprocess.cc ../utils/image_utils.c ../utils/file_utils.c ../utils/image_drawing.c ) 定义了一个变量 SOURCES,它包含了一系列源文件的路径。
add_library(yolov8_seg STATIC ${SOURCES}) 其作用是生成一个名为 yolov8_seg 的静态库,这个静态库包含了变量 SOURCES 中列出的所有源文件。 |
关于include_directories和target_include_directories的区别(link_libraries与target_link_libraries也同理)
关于target_include_directories、target_link_directories、target_link_libraries的区别,什么情况下使用
指定C编译器的路径。 指定C++编译器的路径。 |
set(CMAKE_C_COMPILER "arm-linux-gnueabihf-gcc") set(CMAKE_CXX_COMPILER "arm-linux-gnueabihf-g++") |
设置交叉编译的目标文件的根目录 (当前为/mnt) |
set(CMAKE_SYSROOT $ENV{SYSROOT}) |
设置查找头文件、库文件和可执行文件的根路径(当前为/mnt) |
set(CMAKE_FIND_ROOT_PATH $ENV{SYSROOT})
|
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM:设置为NEVER,表示CMake不会在 CMAKE_FIND_ROOT_PATH 中查找可执行文件。
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE:设置为 ONLY,表示 CMake 只在 CMAKE_FIND_ROOT_PATH 中查找包(如通过 find_package() 查找的库)。
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY:设置为 ONLY,表示 CMake 只在 CMAKE_FIND_ROOT_PATH 中查找库文件。
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE:设置为 ONLY,表示 CMake 只在 CMAKE_FIND_ROOT_PATH 中查找头文件。
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) |