CMake

参考:
https://zhuanlan.zhihu.com/p/367808125
https://zhuanlan.zhihu.com/p/368701263
https://zhuanlan.zhihu.com/p/371257515

一、CMake应用示例

项目文件夹结构

cmake-template
├── CMakeLists.txt
└── build
└── include
└── src
    └── c
        └── main.c

main

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	std::cout << "demo" << std::endl;

    #ifdef TEST_IT_CMAKE
    std::cout<<"TEST_IT_CMAKE is ON"<<std::endl;
    #else
    std::cout<<"TEST_IT_CMAKE is OFF"<<std::endl;
    #endif

    cv::Mat img;
    img = cv::imread("test.jpg");
    cv::imshow("img", img);
    cv::waitKey(0);

	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(cmake_test)
add_executable(demo src/c/main.cpp src/c/imgprocess.cpp)

option(TEST_IT_CMAKE "test option and add_definitions" OFF)
message("TEST_IT_CMAKE " ${TEST_IT_CMAKE})
if(TEST_IT_CMAKE)
    add_definitions(-DTEST_IT_CMAKE)
    message("TEST_IT_CMAKE " ${TEST_IT_CMAKE})
endif()

include_directories(${PROJECT_SOURCE_DIR}/include) #设置项目的include路径,统一管理include。PROJECT_SOURCE_DIR为包含PROJECT()的最近一个CMakeLists.txt文件所在的文件夹。

set(CMAKE_C_COMPILER "/opt/Mrc05Sdk/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc")#设置C编译器
set(CMAKE_CXX_COMPILER "/opt/Mrc05Sdk/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++")#设置c++编译器
set(CMAKE_CXX_STANDARD 14) #指定cpp语言标准
set(CMAKE_BUILD_TYPE Debug) #配置编译类型
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") #配置编译选项

find_package(OpenCV REQUIRED) #搜索opencv
include_directories(${OpenCV_INCLUDE_DIRS}) #include_directories添加include目录
target_link_libraries(demo ${OpenCV_LIBS}) #target_link_libraries声明构建此可执行文件需要链接的库

cmake_minimum_required:制定CMake的最小版本
project:设置工程的名字
add_executable:设置可执行文件(demo)名称,以及添加编译的文件(main.cpp),添加的编译文件可以用相对CMakeLists.txt的相对路径
option:通过选项开关option设置一个变量TEST_IT_CMAKE的状态,再通过add_definitions将TEST_IT_CMAKE转化成源文件中的全局的宏定义#define TEST_IT_CMAKE。从CMakeLists.txt直接控制程序的条件编译。
其中message命令用于消息打印,其实就是打印log,在执行CMake时用来打印不同信息

    #ifdef TEST_IT_CMAKE
    std::cout<<"TEST_IT_CMAKE is ON"<<std::endl;
    #else
    std::cout<<"TEST_IT_CMAKE is OFF"<<std::endl;
    #endif

CMakeLists.txt中TEST_IT_CMAKE 设置为ON,程序输出:TEST_IT_CMAKE is ON
CMakeLists.txt中TEST_IT_CMAKE 设置为OFF,程序输出:TEST_IT_CMAKE is OFF

参考:https://blog.csdn.net/qq_35699473/article/details/115837708

set(CMAKE_C_COMPILER "/opt/Mrc05Sdk/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc")#设置C编译器
set(CMAKE_CXX_COMPILER "/opt/Mrc05Sdk/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++")#设置c++编译器
set(CMAKE_CXX_STANDARD 14) #指定cpp语言标准
set(CMAKE_BUILD_TYPE Debug) #配置编译类型
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") #配置编译选项

(一)cmake变量

在这里插入图片描述

1、in-source build VS out-of-source build

https://www.jianshu.com/p/dee1d8c4cbe0

1.in-source build 是指在 CMakeLists.txt所在的文件夹直接执行cmake
out-of-source build是指在非CMakeLists.txt目录执行cmake

2.两者的重要差异在于产生的中间目标文件(.obj)和可执行程序的位置,in-source build是这些文件和代码混杂在一起,而out-of-source build是在执行cmake的目录集中放置,在项目实际开发中,一般采用out-of-source build方式。而这两者的差异也在CMake的常量上有所体现。以CMAKE_BINARY_DIR(二进制目录)和CMAKE_SOURCE_DIR(代码目录)为例:
1)对于out-of-source buildCMAKE_BINARY_DIR(二进制目录)自然是执行cmake的目录,即二进制中间目标文件存放位置的顶级目录。而CMAKE_SOURCE_DIR是CMakeLists.txt所在的位置。CMake中其余的*_BINARY_DIR和 *_SOURCE_DIR变量的区别与示例完全一致。
2)对于in-source build,由于二进制中间文件和代码目录放在一起,所以这两个变量的取值是相同的。

2.常见变量

https://zhuanlan.zhihu.com/p/603137782#:~:text=CMake%E5%B8%B8%E7%94%A8%E5%8F%98%E9%87%8F%E5%92%8C%E5%B8%B8%E7%94%A8%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%201%20%E4%B8%80%E3%80%81CMake%E5%8F%98%E9%87%8F%E5%BC%95%E7%94%A8%E5%92%8C%E5%AE%9A%E4%B9%89%20%EF%BC%881%EF%BC%89%E4%BD%BF%E7%94%A8%24%20%7B%7D%E8%BF%9B%E8%A1%8C%E5%8F%98%E9%87%8F%E7%9A%84%E5%BC%95%E7%94%A8%E3%80%82%20…%202%20%E4%BA%8C%E3%80%81CMake%E5%B8%B8%E7%94%A8%E5%8F%98%E9%87%8F,5%20%E4%BA%94%E3%80%81%E4%B8%BB%E8%A6%81%E7%9A%84%E5%BC%80%E5%85%B3%E9%80%89%E9%A1%B9%20CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS%20%EF%BC%8C%E7%94%A8%E6%9D%A5%E6%8E%A7%E5%88%B6IF%20ELSE%20%E8%AF%AD%E5%8F%A5%E7%9A%84%E4%B9%A6%E5%86%99%E6%96%B9%E5%BC%8F%E3%80%82%20…%20%E6%9B%B4%E5%A4%9A%E9%A1%B9%E7%9B%AE

1)CMAKE_BINARY_DIR、PROJECT_BINARY_DIR、_BINARY_DIR三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,暂时可以理解为他们是一致的。

(2)CMAKE_SOURCE_DIR、PROJECT_SOURCE_DIR、_SOURCE_DIR这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在in source 编译时,他跟 CMAKE_BINARY_DIR 等变量一致。PROJECT_SOURCE_DIR 跟其他指令稍有区别,暂时理解为他们是一致的。

(3)CMAKE_CURRENT_SOURCE_DIR指的是当前处理的CMakeLists.txt 所在的路径。

(4)CMAKE_CURRRENT_BINARY_DIR,如果是in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是out-of-source 编译,他指的是target 编译目录。使用ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。

(5)CMAKE_CURRENT_LIST_FILE输出调用这个变量的CMakeLists.txt 的完整路径。

(6)CMAKE_CURRENT_LIST_LINE输出这个变量所在的行。

(7)CMAKE_MODULE_PATH这个变量用来定义自己的cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些cmake 模块,这些cmake 模块是随你的工程发布的,为了让cmake 在处理 CMakeLists.txt 时找到这些模块,你需要通过SET 指令,将自己的cmake 模块路径设置一下。
例如:

SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

这时候你就可以通过INCLUDE 指令来调用自己的模块了。

(8)EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH分别用来重新定义最终结果的存放目录。

(9)PROJECT_NAME返回通过PROJECT 指令定义的项目名称。
(10)CMAKE_BUILD_TYPE、CMAKE_CXX_FLAGS、CMAKE_CXX_FLAGS_RELEASE

    set(CMAKE_BUILD_TYPE Release CACHE STRING "Set C++ Compiler Flags" FORCE) # Release or Debug
    set(CMAKE_CXX_FLAGS"-Wall -Wextra")
    set(CMAKE_CXX_FLAGS_RELEASE"-O3")

1.CMAKE_BUILD_TYPE
CMake内置的变量,用于指定构建目标的类型。在这里,它被设置为Release,表示优化后的发布版本。也可以将它设置为Debug来启用调试信息和符号表。
2.CMAKE_CXX_FLAGS
CMake内置的变量,用于设置C++编译器的标志。
"-Wall -Wextra"是需要设置的C++编译器标志。-Wall表示开启所有警告,-Wextra表示开启额外的警告。
3.CMAKE_CXX_FLAGS_RELEASE
CMake内置的变量,用于设置C++编译器在Release模式下的标志。
"-O3"是需要设置的C++编译器标志。-O3表示采用最高级别的优化,以便在编译Release版本时获得更高的性能。

(二)find_package

find_package(OpenCV REQUIRED) #搜索opencv
include_directories(${OpenCV_INCLUDE_DIRS}) #include_directories添加include目录
target_link_libraries(demo ${OpenCV_LIBS}) #target_link_libraries声明构建此可执行文件需要链接的库

REQUIRED选项表示如果报没有找到的话,cmake的过程会终止,并输出警告信息。可以看到在执行find_package(OpenCV 3 REQUIRED)命令后,CMake找到了我们安装的位于/usr/local下的OpenCV库,并设置了CMake变量OpenCV_DIR为OpenCV库的配置文件所在路径,正是通过载入这个路径下的OpenCVConfig.cmake配置文件才能配置好OpenCV库,然后在OpenCVConfig.cmake配置文件中定义了变量OpenCV_INCLUDE_DIRS为OpenCV库头文件包含路径,这样我们才能才在代码中使用#include <opencv2/opencv.hpp>而不会出现编译错误,同时定义了变量OpenCV_LIBS为OpenCV链接库路径,这样我们才能正确链接到OpenCV中的库文件,而不会出现类似未定义的引用这样的链接错误。

find_package用法详见:https://blog.csdn.net/zhanghm1995/article/details/105466372!

(三)include_directories、link_directories、add_library、target_link_libraries

1.include_directories
添加头文件目录
2.link_directories
添加需要链接的库文件目录,需要写在target_link_libraries之前。使用 link_directories 命令仅仅告诉链接器搜索库文件的路径,并不会自动链接这些库文件。要链接库文件,你仍然需要使用 target_link_libraries 命令来指定要链接的库。该指令有时候不一定需要。因为find_package和find_library指令可以得到库文件的绝对路径。不过你自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。尽量避免使用 link_directories 命令。而是优先使用 target_link_libraries 命令来显式指定要链接的库文件,或者使用 Find 模块来查找库文件。这样可以更好地处理库文件的位置和依赖关系,使项目更加可移植和可靠。(个人理解:使用link_directories并不能直观显示哪个库对应了哪个文件)
3.target_link_directories

target_link_libraries(target_name [PRIVATE|PUBLIC|INTERFACE] library1 library2 ...)

用于为指定目标添加链接器搜索库文件的目录路径。library1 library2这些路径可以是绝对路径,也可以是相对于 CMakeLists.txt 文件的相对路径。target_name为指定要操作的目标的名称,可以是可执行目标或库目标的名称。
4.add_library
该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。使用再add_executable前。
target_link_libraries
该指令的作用为将目标文件与库文件进行链接。使用在add_executable后。

https://blog.csdn.net/bigdog_1027/article/details/79113342

(四)set_target_properties设置目标属性

set_target_properties(target_name PROPERTIES property1 value1 property2 value2 ...)

target_name 是要设置属性的目标的名称。
property1, property2, … 是要设置的属性的名称。
value1, value2, … 是要设置的属性的值。
通过 set_target_properties 命令,可以设置目标的各种属性,例如输出路径、编译器选项、链接器选项等。

例子:

set_target_properties(Camera3dPls PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
        LINK_FLAGS "-Wl,--disable-new-dtags,-rpath-link,/opt/Mrc05Sdk/sysroot/usr/lib/aarch64-linux-gnu:/opt/Mrc05Sdk/sysroot/usr/lib,-rpath,/userdata/CarryBoy/CAM"
    )

1.Camera3dPls:项目的名称,是target_name
2.RUNTIME_OUTPUT_DIRECTORY:设置项目生成的输出路径
3.LINK_FLAGS:设置链接选项的一个属性,用于指定目标的链接标志。
  rpath-link 选项:
1)用于告诉链接器在链接时要使用的附加库搜索路径。
2)该选项不直接影响生成的可执行文件或共享库的动态标签,仅在链接时用于搜索库的位置。
3)可以有多个 rpath-link 选项,每个选项后面都跟着一个搜索路径。
4)rpath-link 选项通常用于指定额外的搜索路径,以便在链接时找到依赖库,但不会修改生成的可执行文件或共享库的运行时搜索路径。(运行时搜索路径可以通过rpath动态标签来设置)
  rpath 动态标签:
1)rpath 是动态标签(DT_RPATH 或 DT_RUNPATH),用于指定生成的可执行文件或共享库的运行时库搜索路径
2)当给定可执行文件或共享库运行时,链接器会根据 rpath 动态标签指定的路径搜索所需的依赖库。
3)可以通过链接选项(如 -Wl,-rpath,/path/to/library)将 rpath 指定为链接选项,或者可以使用 set_target_properties 或类似的方法在 CMake 中指定它。
4)通常情况下,rpath 是更常用和推荐的设置运行时库搜索路径的方法。

(五)option

option(<option_name> "Description of the option" <initial_value>)

在CMake中,可以使用option命令来定义和使用选项。选项可以用来在构建过程中开启或关闭某些特性。
其中,<option_name>是选项的名称,Description of the option是对选项的描述,<initial_value>是选项的初始值(ON表示开启,OFF表示关闭)。

option(ARM "Enable ARM" ON)
option(X86 "Enable X86" OFF)

if(ARM)
    message("ARM ON!")
elseif(X86)
    message("X86 ON!")
endif()

(六)利用if设置不同的CMAKE_BUILD_TYPE

STREQUAL:判断字符串是否相等,判断时并且区分大小写
CMAKE_CXX_FLAGS_DEBUG、CMAKE_CXX_FLAGS_RELEASE:CMake预定义的内建变量,且他们是全局的。
-O0: 这是一个优化级别的选项,在这里表示禁用所有优化。这样可以确保调试时生成的代码更容易理解和调试。
-Wall: 这个选项启用了编译器的警告信息,并显示警告消息。开启这个选项可以帮助发现潜在的问题和不规范的代码。
-g2: 这个选项启用了调试信息的生成。其中的 2 表示生成详细的调试信息,包括函数名、局部变量等。这样可以更方便地在调试器中进行代码调试。
-ggdb: 这个选项指定了调试信息的格式和版本。-ggdb 是用于与 GDB 调试器兼容的调试信息格式,这样 GDB 可以正确地读取和解析调试信息。

set(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")  

if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
    message("Debug build:" ${CMAKE_CXX_FLAGS_DEBUG})
elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release")
    message("Release build" ${CMAKE_CXX_FLAGS_RELEASE})
else()
    message("Unknown build type")
endif()

(七)设置C++标准

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

C++ 语言有不同版本的标准,如 C++ 98、C++ 11、C++ 14 等。不同标准提供的特性有所区别,故有必要在 CMakeLists.txt 中为项目指定使用何种标准,进而让 CMake 使用正确的编译器标志(比如 -std=c++11)。
对于 CMake v3.1 以上版本,可以通过 CMAKE_CXX_STANDARD 变量指定标准,或通过 target_compile_features 函数根据使用的特性自动推断适用于目标的编译器标志。

方法1.

1.CMAKE_CXX_STANDARD 变量用于设置创建目标时使用的 CXX_STANDARD 目标属性默认值。

# 设置允许使用的 CMake 最小版本(不能低于 3.1cmake_minimum_required(VERSION 3.1)

# 设置项目名称
project (hello_cpp11)

# 将 C++ 标准设置为 C++ 11
set(CMAKE_CXX_STANDARD 11)

# 添加 executable
add_executable(hello_cpp11 main.cpp)

出于对编译器的兼容性之考虑,上面这种设置标准的方法并不是强制执行。假设某个项目设置使用 C++ 11 标准,但用户使用的编译器并不支持 -std=gnu++11 (或等价的)标志,将不会导致错误或警告,而是在允许的情况下添加 -std=gnu++98 标志。换言之,CMake 将自动“衰减”至最接近的标准。

2.CXX_STANDARD_REQUIRED。如果确实需要强制指定标准,禁用这种自动衰减调整,那么可以通过设置 CXX_STANDARD_REQUIRED 实现。
CXX_STANDARD_REQUIRED 是一个布尔类型的变量,用于描述是否需要(强制)指定 CXX_STANDARD。当打开此选项,且当前使用的编译器不支持指定的标准时,会在 configuring 阶段报错失败,不会进行编译。

cmake_minimum_required(VERSION 3.1)

project (hello_cpp11)

set(CMAKE_CXX_STANDARD 11)  # 将 C++ 标准设置为 C++ 11
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # C++ 11 是强制要求,不会衰退至低版本

add_executable(hello_cpp11 main.cpp)

3.CMAKE_CXX_EXTENSIONS禁用编译器特有扩展
另一个经常与 CMAKE_CXX_STANDARD 一同设置的布尔变量是 CMAKE_CXX_EXTENSIONS。该属性用于指定是否使用编译器特有的扩展(即,不同的编译器在 C++ 标准之外自行实现的、非通用的特性)。对于某些编译器来说,启用此选项后会在编译行中添加特殊的标志,比如用 -std=gnu++11 替换 -std=c++11。该属性默认为 ON。如果某个项目对可移植性有较高的要求,可能为不同的平台使用不同的编译器,那么建议将其设置为 OFF。

cmake_minimum_required(VERSION 3.1)

project (hello_cpp11)

set(CMAKE_CXX_STANDARD 11)  # 将 C++ 标准设置为 C++ 11
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # C++ 11 是强制要求,不会衰退至低版本
set(CMAKE_CXX_EXTENSIONS OFF)  # 禁止使用编译器特有扩展

add_executable(hello_cpp11 main.cpp)

4.目标级设置
在上面几节的例子中,均是通过 set(CMAKE_CXX_* value) 的方式设置 CXX_* 全局级别的变量来实现,这也是最为推荐的做法。在极少数情况下,可能需要为项目中的不同目标设置不同的 C++ 标准(比如某项目其实更像是由几个较独立的子部分构成、且这些不同的部分不能使用相同的标准),可以通过设置目标属性的方法来为每个目标细致调整:

set_target_properties(myTarget PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED ON
    CXX_EXTENSIONS OFF
)

再次强调,除非有非常充分的理由,否则不要这样设置。

方法2.target_compile_features 函数

(八)缓存变量

https://blog.csdn.net/m0_57845572/article/details/118400027

CMake 存储一组单独的 “缓存变量” 或 “缓存条目”,因为在项目构建树中的多次运行时,我们需要变量的值保持不变。

1.如何引用缓存的变量?
使用 $CACHE{} 或 ${} 引用缓存条目的值。

2.如何设置或取消缓存的变量?
一般的情况下,我们不会在源码文件中去设置或取消设置缓存的变量,我们只会引用它。
可以通过-D在命令行中修改设置为FORCE的缓存变量。

# 设置缓存条目
set(<variable> <value> ... CACHE <type> <docstring> [FORCE])
#这里的type 可以是STRING BOOL FILEPATH PATH ,但是要根据前面 value 类型来确定。
#这里的docstring为说明性文档
# 例子:
set(CMAKE_BUILD_TYPE Release CACHE STRING "Set C++ Compiler Flags" FORCE) # Release or Debug

# 取消缓存条目
unset(<variable> CACHE)

什么是 CMakeCache.txt和FORCE?
CMakeCache.txt 文件用于存储缓存条目,第一次构建时会生成该文件,之后的构建并不会创建该文件。在引用缓存条目时,会去查找该文件,并返回值。

在使用 set() 命令时,默认情况下,如果缓存条目在 CMakeCache.txt 文件不存在,会创建缓存条目,并写入到 CMakeCache.txt 文件中。如果缓存条目在 CMakeCache.txt 文件存在,忽略 set() 命令,不会覆盖现有的缓存条目。但是我们想强制覆盖现有的缓存条目,我们可以 FORCE 选项。

(九)macro宏

https://blog.csdn.net/fengbingchun/article/details/127145153

macro的定义格式如下:定义后可作为命令供调用

macro(<name> [<arg1> ...])
  <commands>
endmacro()

#例子
macro(add_deployment_file SRC DEST)
    file(RELATIVE_PATH path ${PROJECT_SOURCE_DIR}
         ${PROJECT_BINARY_DIR})
    file(APPEND "${PROJECT_BINARY_DIR}/QtCreatorDeployment.txt"
         "${path}/${SRC}:${DEST}\n")
endmacro()

#调用
add_deployment_file("bin/Camera3dPls" "CarryBoy/CAM")
Add_Deployment_File("bin/Camera3dPls" "CarryBoy/CAM")

其中name是macro的名字,参数为arg1,arg2等。与function一样,macro名称也不区分大小写,但强烈建议使用macro定义中声明的相同名称。通常,macro使用全小写名称。

(十)file命令

https://blog.csdn.net/weixin_42730667/article/details/122568165

1.RELATIVE_PATH获取相对路径

例子:
计算 ${PROJECT_BINARY_DIR} 相对于 ${PROJECT_SOURCE_DIR} 的路径,并将结果存储在变量 path 中。

file(RELATIVE_PATH path ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})

2.WRITE写入\APPEND追加文件内容

file(WRITE <filename> <content>)
file(APPEND <filename> <content>)

filename为文件名(包含文件的路径)
content为写入的内容

macro(add_deployment_file SRC DEST)
    file(RELATIVE_PATH path ${PROJECT_SOURCE_DIR}
         ${PROJECT_BINARY_DIR})
    file(APPEND "${PROJECT_BINARY_DIR}/QtCreatorDeployment.txt"
         "${path}/${SRC}:${DEST}\n")
endmacro()

3.GLOB搜索文件并储存到变量中

#搜索当前路径下*.txt,并将所有符合条件的相对/home/dmh/program/路径保存到TEST_FILE_GLOB中
file(GLOB TEST_FILE_GLOB true RELATIVE /home/dmh/program/ *.txt )
message("TEST_FILE_GLOB: " ${TEST_FILE_GLOB})

file(GLOB …) 是 CMake 中的一个命令,用于将符合特定模式的文件列表存储到一个变量中。

4.Generate

给当前 CMake Generator 支持的每一个构建配置产生一个输出文件。判断生成器表达式,为真则将内容写到指定的文件中。

file(GENERATE OUTPUT output-file
     <INPUT input-file|CONTENT content>
     [CONDITION expression])

CONDITION <condition> : 对条件为真的特定配置产生输出文件。在计算生成器表达式之后条件必须为0或者1.

CONTENT <content> :使用明确指定的内容作为输入。

INPUT <input-file> : 从指定文件获取内容作为输入。如果是相对路径认为是相对于 CMAKE_CURRENT_SOURCE_DIR 。详情查看 CMP0070 规则。

OUTPUT <output-file> :指定产生的输出文件名。使用生成器表达式(例如$)产生特定的配置输出文件名。如果生成内容一致,则多个配置可能产生相同输出文件。否则 必须给每个配置计算一个单独的文件名。相对路径(在计算生成器表达式之后)被认为是相对于 CMAKE_CURRENT_BINARY_DIR 。详情查看 CMP0070 规则。

必须给定确切的一个 CONTENT 或 INPUT 。一个特定的 OUTPUT 文件最多被一个 file(GENERATE) 调用(invocation)命名。在后续 cmake 运行时,只有当其内容修改了,生成文件才会被修改且对应的时间戳被更新。

还要注意,file(GENERATE)直到生成阶段才会创建输出文件。当 file(GENERATE) 命令返回时输出文件还没有被写入,只有当处理完整个项目的 CMakeLists.txt 文件才会被写入

例子:

#将"STREQUAL:str1,str2: $<STREQUAL:str1,str2>\n"写入到当前目录的generator_test.txt文件中。
file(GENERATE OUTPUT "./generator_test.txt" CONTENT "STREQUAL:str1,str2: $<STREQUAL:str1,str2>\n")

(十一)get_filename_component命令

https://blog.csdn.net/wzj_110/article/details/116431616

获取’文件名’的’特定’部分
注:扩展名就是文件的类型后缀。比如,test.txt,txt为该文件的扩展名。

get_filename_component(<var> <filename> <MODE> [CACHE])
var:变量名
filename:完整的文件(路径+文件名)
MODE:要获取的文件名的具体部分
	  DIRECTORY:文件所在路径
	  NAME:没有目录的文件名
	  EXT:最长的扩展名,即从第一个.开始
	  NAME_WE:没有目录和扩展名的文件名
	  LAST_EXT:最后一个扩展名
	  NAME_WLE:没有目录和最后一个扩展名的文件名
CACHE:如果指定,则获取得到的结果变量会放到缓存中。

(十二)指定交叉编译环境的目录

https://blog.csdn.net/qq_32966261/article/details/127937389
https://zhuanlan.zhihu.com/p/100367053

#指定交叉编译环境的目录
set(CMAKE_FIND_ROOT_PATH "${ARM_SYSROOT}")
#从来不在指定目录(交叉编译)下查找工具程序。(编译时利用的是宿主的工具)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
#只在指定目录(交叉编译)下查找库文件
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
#只在指定目录(交叉编译)下查找头文件
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
#只在指定的目录(交叉编译)下查找依赖包
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

1.为什么要指定交叉编译环境路径
通常,我们在开发时,需要使用系统库或第三方库的功能,在生成可执行文件时,将其进行链接。cmake 提供了 FIND_PROGRAM(),FIND_LIBRARY(), FIND_FILE(), FIND_PATH() 和 FIND_PACKAGE() 实现相应的查找功能。如果我们在进行交叉编译时使用了上述指令,那么并不能生成可执行文件。因为默认情况下,上述指令查找的是主机上的相关文件,其并不适用于目标机器。还好,cmake 为我们提供了相应的变量:

2.如何指定交叉编译环境的目录
CMAKE_FIND_ROOT_PATH:设置其值为一系列的目录(set(CMAKE_FIND_ROOT_PATH path1 path2 path3 …),这样在执行 FIND_XXX() 指令时就会从这一系列的目录中进行查找。跟随该变量的有下述 3 个变量,它们的值为 NEVER 、 ONLY 或 BOTH:

3.以CMAKE_FIND_ROOT_PATH_MODE_PROGRAM为例子:
如果设置为 NEVER,那么 CMAKE_FIND_ROOT_PATH 就不会对 FIND_PROGRAM() 产生影响, FIND_PROGRAM() 不会在 CMAKE_FIND_ROOT_PATH 指定的目录中寻找;
如果设置为 ONLY,那么 FIND_PROGRAM() 只会从CMAKE_FIND_ROOT_PATH 指定的目录中寻找;
如果设置为 BOTH,那么 FIND_PROGRAM() 会优先从 CMAKE_FIND_ROOT_PATH 指定的目录中寻找,再从默认的目录中寻找。

因为 FIND_PROGRAM() 大部分情况下用于寻找可执行程序,给后续的 EXECUTE_PROCESS() 或 ADD_CUSTOM_COMMAND() 指令使用。并且,只有主机在生成编译文件时使用该可执行程序。因此通常设置 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 为 NEVER(set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER);

CMAKE_FIND_ROOT_PATH_MODE_LIBRARY:由于在进行交叉编译,所以只能使用 FIND_LIBRARY() 查找符合目标机器的库文件,因此设置该变量值为ONLY(set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)),表示只从 CMAKE_FIND_ROOT_PATH 指定的目录中查找;

CMAKE_FIND_ROOT_PATH_MODE_INCLUDE:同上,将其值设置为 ONLY。

(十三)添加源文件的几种方法

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

建议把头文件也加上,这样在 VS 里可以出现在“Header Files”一栏。

(十四)set_property() 命令

https://blog.csdn.net/rangfei/article/details/126051723

1.在开发过程碰到需要在上级目录中构建,而源代码又分别写在下级目录的情况。所以考虑将需要编译的源代码放到一个 cmake 列表中。但是 set() 对应生成的变量都在不同的目录下不共用。可以使用set_property() 命令,将下级cmakelists中获取的源代码路径传递到根目录中的cmakelists。

目录结构:

$ tree
├── CMakeLists.txt              
├── linux
│   ├── CMakeLists.txt
│   ├── property.c
│   └── property.h
├── main.c
└── win
    ├── CMakeLists.txt
    ├── property.c
    └── property.h

main.c

#include <stdio.h>
 
#include <property.h>
 
int main()
{
    show_system();
 
    return 0;
}

property.c

#include <stdio.h>
 
#include "property.h"
 
void show_system()
{
    printf("This is linux\n");    // in linux
    printf("This is windows\n");  // in win
}

根目录下CMakeList.txt
可以根据不同的情况(Linux/Win)选择性地添加不同的源代码进行编译

cmake_minimum_required (VERSION 3.13.0)
 
project (property_test VERSION 0.0.4)
 
# 设置全局属性 SOURCE_LIST
set_property( GLOBAL APPEND PROPERTY SOURCE_LIST)
 
# 如果是 Linux 系统,选择编译 linux 目录
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
    include_directories (linux)
    add_subdirectory (linux)
 
# Window 系统下选择编译 win 目录
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Windows")
    include_directories (win)
    add_subdirectory (win)
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
 
# 将 SOURCE_LIST 的内容保存到 SRC_LIST 中
get_property(SRC_LIST GLOBAL PROPERTY SOURCE_LIST )
 
message("src list:" ${SRC_LIST})
 
# build exec
SET(exename "property")
add_executable (${exename} ${SRC_LIST} main.c)

子目录下的 CMakeLists.txt

file(GLOB_RECURSE SRC_LIST "*.cpp" "*.c")    # 查找当前目录下所有 .cpp 和 .c 文件
set_property( GLOBAL APPEND PROPERTY SOURCE_LIST ${SRC_LIST})   # 将这些文件路径附加到 SOURCE_LIST 后面

2.设置目标属性

set_property(TARGET detection_camera PROPERTY POSITION_INDEPENDENT_CODE 1) 

用于设置CMake构建系统中名为 detection_camera 的目标(通过 TARGET 参数指定)的属性。

具体来说,这个语句设置 detection_camera 目标的 POSITION_INDEPENDENT_CODE 属性为1。这个属性用于指示编译器生成的目标文件是否需要是可独立位置代码(PIC)。

独立位置代码(PIC)是一种编译选项,用于生成与位置无关的代码,这样生成的目标文件可以在内存中的任意位置加载和运行。这对于生成共享库(动态链接库)是很重要的,因为共享库可以在不同的进程中加载和执行。

通过将 POSITION_INDEPENDENT_CODE 属性设置为1,您告诉CMake构建系统要求编译器生成的 detection_camera 目标文件是位置无关代码。这在将目标文件链接到共享库时非常有用。

(十五)生成器表达式

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

1. 概述

生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。比如:
1)条件链接,如针对同一个编译目标,debug版本和release版本链接不同的库文件
2)条件定义,如针对不同编译器,定义不同的宏
所以可以看到,其中的要点是条件,之所以需要自动生成,那绝大多数时候肯定是因为开发者无法提前确定某些配置,不能提前确定那往往就是有条件的。

生成器表达式的格式形如$<…>,可以嵌套,可以用在很多构建目标的属性设置和特定的CMake命令中。值得强调的是,生成表达式被展开是在生成构建系统的时候,所以不能通过解析配置CMakeLists.txt阶段的message命令打印

2.调试方法

调试
调试可以通过输出到文件的方式,在cmake执行完之后去检查是否符合预期,比如:

set(str1 "s1")
set(str1 "s2")
file(GENERATE OUTPUT "./generator_test.txt" CONTENT "STREQUAL:str1,str2: $<STREQUAL:str1,str2>\nBOOL: $<BOOL:true>")

在当前目录下生成generator_test.txt文件,文件中内容如下:

STREQUAL:str1,str2: 0
BOOL: 1

3.常用表达式

cmake官方文档:https://cmake.org/cmake/help/v3.5/manual/cmake-generator-expressions.7.html

1.$<INSTALL_INTERFACE:...>
Content of … when the property is exported using install(EXPORT), and empty otherwise.
当表达式对应的属性被install(EXPORT)时,才会采用$<INSTALL_INTERFACE:...>中…的内容

2.$<BUILD_INTERFACE:...>
Content of … when the property is exported using export(), or when the target is used by another target in the same buildsystem. Expands to the empty string otherwise.
当表达式对应的属性被export()或者被同一个构建系统使用时,才会采用$<BUILD_INTERFACE:...>中…的内容

(十六)PUBLIC、PRIVATE、INTERFACE关键字

CMake中经第使用target…()类似的命令,一般这样的命令支持通过 PUBLIC. PRIVATE.TNTEREACE关键字来控制传递。
以 target 1ink library(A B) 为例,从理解的角度来看
•PRIVATE:依赖项B仅链接到目标A,如果有C 链接了A,。不会链接B
•INTERPACE:依赖项B井不链按到目标A,如果有C链接了A,C会链接B
•PUBLIC:依赖项B链接到目标入,如果有C链接了A,C也会链接B
其实就是对象属性的传递,打个散烟的比方:
• PRIVATE:就是自己抽,不给别人抽
•INTERFACE:就是自己不抽,给别人抽
• PUBLIC:就是自己抽,也给别人抽
从使用的角度来说,如果有C链接了目标A
•如果B仅用于A的实现,且不在头文件中提供给c使用,使用 PRIVATE
•如果B不用于A的安现,仅在头文件中作为接口给c使用,使用 INTERFACE
•如果B既用于A的实现,也在头文件中提供给c使用,使用PUBLIC

二、模块化管理项目

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值