【CMakeLists】学习笔记(指令详解&&即学即用&&快速入门哦)

编写CMakeLists.txt最常用的功能就是调用其他的头文件(*.h *.hpp )、动态链接库(*.so)、静态链接库(*.a),将源文件*.cpp *.c *.cc 编译成目标可执行或目标可链接库文件CMakemakefile的上层工具,用于跨平台构建环境,生成可移植的makefile,并简化自己动手写makefile时的巨大工作量。

cmake安装

sudo apt install cmake
# 查看cmake版本
cmake --version
# 命令(..表示CMakeLists所在目录,当前目录为build)
cmake ..
# 可视化界面调试,安装命令
sudo apt install cmake-curses-gui
# 命令(..表示CMakeLists所在目录,当前目录为build)
ccmake ..

CmakeLists中的注释与变量

注释

行注释使用#; 块注释(3.0开始才有)使用#[[xxxx]],一些编辑器和IDE还不支持它。比如:

# 单行注释
# Multi line comments follow
# 多行注释

#[[
xxxx
]]

变量

程序变量

CMake中程序变量使用setunset命令设置或者取消设置变量。作用域只是在CMakeLists.txt里!设置的变量可以是字符串,数字或者列表 ,语法: set(变量名 变量值) 比如:

# Set variable
set(AUTHOR_NAME Farmer)
set(AUTHOR "Farmer Li")
set(AUTHOR Farmer\ Li)

# Set list
set(SLOGAN_ARR To be)   
set(SLOGAN_ARR To;be)
set(SLOGAN_ARR "To;be")

set(NUM 30)   # Saved as string, but can compare with other number string
set(FLAG ON)  # Bool value

注意点

  • 如果要设置的变量值包含空格,则需要使用双引号或者使用 \ 转义,否则可以省略双引号;

  • 如果设置多个值或者字符串值的中间有";“,则保存成list,同样是以”;"分割的字符串;

  • 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表;

  • 引用变量:${variable},在if()条件判断中可以简化为只用变量名。

  • unset(variable) 删除变量,取消设置

环境变量

CMake允许设置环境变量,环境变量通过特殊的形式$ENV{varName}获取,通过SET(ENV{变量名} "变量值")设置写入环境变量

注意点

  • 设置环境变量作用只是在CMakeLists.txt编译结束后就没了, 所以一般我们就来获取环境变量而已,和判断是否存在

共有变量(见附录)

提供信息的变量可以提供某种信息,通常只需要读取变量即可,而不需要对变量进行修改。


CMakeLists基本结构

一般,项目工程文件中:

  1. bin 文件夹存放 编译好的可执行二进制文件

  2. include 文件夹存放头文件

  3. src 文件夹存放源代码

  4. lib 文件夹存放编译好的库文件或者要调用的库文件

# 设定最低版本为3.4.0
cmake_minimum_required(VERSION 3.4.0)
# 设定工程项目名称为planning
project(planning)                                        


# 设定项目编译所需头文件目录
include_directories(
	/usr/lib/x86_64-linux-gnu
	include
	)


# 设定编译中用到的源文件
# 源文件很多(一句命令搞定)
aux_source_directory(./src SRC_FILES)
# file自定义搜索源文件,塞给集合SRC_FILES
file(GLOB SRC_FILES src/*.cpp)
# set 直接设置变量的值
set(SRC_FILES main.cpp test.cpp)

# 设定存放 生成编译好的可执行二进制文件目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设定存放 生成编译好的库文件目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)


# 设定编译类型
# 将.cpp/.c/.cc源文件集合编译生成目标库文件(静、动)
add_library(planning STATIC ${SRC_FILES})
add_library(planning SHARED ${SRC_FILES})
# 将.cpp/.c/.cc源文件集合编译生成目标可执行二进制文件
add_executable(planning ${SRC_FILES})


# 对add_library或add_executable生成的目标库文件或者目标可执行二进制文件进行链接操作
target_link_libraries( generate_pointcloud ${OpenCV_LIBS} ${PCL_LIBRARIES} )

常用命令功能详细说明如下(命令大小写不敏感)

常用set命令

set(变量名 文件名/路径/...)
# 给文件名/路径名或其他字符串起别名,用${变量}获取变量内容

# 如将多个源文件打包并重命名
set(SRC_FILES main.c testFuc1.cpp testFunc2.cc)

#设定可执行二进制文件的目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 将lib输出路径设置为当前工程顶层目录下的lib文件夹下
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)

# 设置编译目标类型是release版还是debug版本
set(CMAKE_BUILD_TYPE "Release")

# 设置编译器
set(CMAKE_CXX_COMPILER "g++")


# 设置C++编译器版本
set(CMAKE_CXX_STANDARD 11)
# 设置c编译器版本
set(CMAKE_C_STANDARD 11) 
# CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加
set(CMAKE_C_FLAGS)
# CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加
set(CMAKE_CXX_FLAGS "-std=c++17")


# BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
set(BUILD_SHARED_LIBS ON) 

设定最低版本

cmake_minimum_required(VERSION 3.4.0)
cmake_minimum_required(VERSION 3.4.0 FATAL_ERROR)
# 设定最低版本3.4.0
#(最开始先声明使用cmake最低版本要求,可选,可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake)
# FATAL_ERROR 表示版本不满足则抛出错误(可选)

设定工程项目名称

project(planning)
project(planning VERSION 1.0.0  LANGUAGES  CXX)  # 项目的名字  版本 1.1.0   编程语言 CXX

# 本CMakeLists.txt的项目名称(命令不是强制性的,但最好都加上)
# 会自动创建两个变量planning_BINARY_DIR 和 planning_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量PROJECT_BINARY_DIR、PROJECT_SOURCE_DIR
# ${PROJECT_SOURCE_DIR}:本CMakeLists.txt所在的文件夹路径
# ${PROJECT_NAME}:本CMakeLists.txt的项目名称(planning)

设定项目编译所需头文件目录(所有用到的.h/.hpp头文件目录

# 命令
include_directions()
target_include_libraries()
include_directions()
# 添加项目编译所需要引用到的所有头文件的目录
# 不需要显式指定这些目录。CMake 会自动搜索系统默认的头文件目录,其中就包括 /usr/include 、 /usr/local/include、/usr/lib/x86_64-linux-gnu
# 因此,只需要在源代码中使用 #include 指令引用所需的头文件,而无需在 CMakeLists.txt 中显式指定这些目录
# 只有在使用了非系统默认的头文件目录时,才需要显式指定目录
set(PROTO_DIR ../proto)
target_include_directories(planning
	include
	${PROTO_DIR}/planningMsg
	${PROTO_DIR}/ImuData
	${OPENCV_INCLUDE_DIRS}
	)

include_librariestarget_include_libraries 两个命令的区别

  • 作用对象不同:

    • include_libraries 命令用于指定项目中所有目标(包括可执行文件和库)的头文件搜索路径。

    • target_include_libraries 命令用于指定特定的目标target)(可执行文件或库)的头文件搜索路径。

  • 使用方式不同

    • include_libraries 命令的语法是将需要添加的头文件搜索路径作为参数传递给命令。

    include_directories(path1 path2 ...)
    • target_include_libraries 命令的语法是将需要添加的头文件搜索路径作为参数传递给特定目标(target)。target_include_directories() 命令将指定的头文件搜索路径应用于特定的目标,可以使用 PUBLICPRIVATEINTERFACE 限定符来控制是否对依赖的目标可见。

    target_include_directories(MyTarget PUBLIC path1 path2 ...)

    include_directories() 用于指定项目中所有目标的头文件搜索路径,而 target_include_directories() 则用于指定特定目标的头文件搜索路径。建议使用 target_include_directories() 命令,因为它提供了更细粒度的控制,并且允许您明确指定特定目标的头文件搜索路径,而不会影响其他目标。

注:PUBLIC PRIVATE INTERFACE 限定符,用于控制目标之间的头文件包含和库链接的可见性,这些限定符的使用允许在 CMake 构建过程中控制头文件包含的可见性,以确保每个目标仅访问其所需的头文件搜索路径,避免了不必要的依赖和潜在的编译错误。类似的限定符也可以在 target_link_libraries() 命令中使用,用于控制库链接的可见性。

命令中未指定任何限定符,默认情况下将应用 PUBLIC 限定符

  • PUBLIC

当使用 PUBLIC 限定符时,指定的头文件搜索路径将应用于当前目标,并传递给当前目标的依赖目标(target_link_libraries() 中的依赖),意味着当前目标和依赖目标都可以访问指定的头文件搜索路径

target_include_directories(MyTarget PUBLIC path1 path2 ...)
# MyTarget 目标和依赖于它的其他目标都可以使用 path1、path2 等指定的头文件搜索路径
  • PRIVATE

当使用 PRIVATE 限定符时,指定的头文件搜索路径仅适用于当前目标,意味着只有当前目标可以访问指定的头文件搜索路径,依赖目标无法访问

target_include_directories(MyTarget PRIVATE path1 path2 ...)
# 只有 MyTarget 目标可以使用 path1、path2 等指定的头文件搜索路径,而依赖于 MyTarget 的其他目标无法访问这些路径

设定编译中用到的源文件

# 方法一:搜索所有的.cpp/.c/.cc文件
# 获取路径(./src)下所有的.cpp/.c/.cc文件,并赋值给变量SRC_FILES中,然后在add_library或add_executable里调用SRC_LIST(注意调用变量时的写法)
aux_source_directory(路径 变量)
aux_source_directory(./src SRC_FILES)

-----------------------
# 方法二: 自定义搜索规则
# file自定义搜索源文件,塞给集合SRC_FILES
file(GLOB SRC_FILES src/*.cpp )

set(PROTO_DIR ../../proto)
file(GLOB SRC_FILES src/*.cpp ${PROTO_DIR}/*.cc )

-----------------------
# 方法三:
# (源文件较少,直接在设置编译类型时明确指定包含哪些源文件),但一般将源文件放至src文件夹下
add_executable(planning planing.cpp localplanning.cpp globleplanning.cpp)
add_executable(planning 
	src/planing.cpp
    src/localplanning.cpp
    src/globleplanning.cpp)
# 以上用方法一,则为
aux_source_directory(./src SRC_FILES)

-----------------------
# 方法四:

# set 直接设置变量的值
set(SRC_FILES main.cpp test.cpp)

# set 追加设置变量的值
set(SRC_LIST main.cpp)
set(SRC_LIST ${SRC_LIST} test.cpp)


# list 追加或者删除变量的值
set(SRC_LIST main.cpp)
list(APPEND SRC_LIST test.cpp)
list(REMOVE_ITEM SRC_LIST main.cpp)
  • 自定义源文件搜索规则
file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST})
# 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
file(GLOB_RECURSE SRC_LIST "*.cpp") #递归搜索
file(GLOB SRC_PROTOCOL RELATIVE "protocol" "*.cpp") # 相对protocol目录下搜索
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

设定编译类型(将.cpp/.c/.cc源文件集合编译生成目标库文件、二进制可执行文件)

将.cpp/.c/.cc源文件集合编译生成目标库文件
add_library(<name> [STATIC | SHARED]   [EXCLUDE_FROM_ALL] [source1] [source2 ...])
# <name> :库的名字(库文件名称通常为libXXX.so),在这里直接写名字即可,不要写lib,会自动加上前缀
# STATIC代表静态库,SHARED代表动态库或者共享库
# (add_library 默认生成是静态库)

#  明确指定包含哪些源文件,单个文件或经set设置的文件合集
# 其实不大区分add_library、add_libraries,均可用单个或多个源文件
add_library(生成库文件名称 STATIC 单个文件或经set设置的文件合集)
add_libraries(生成库文件名称 STATIC 源文件1 源文件2 ...)
add_library(生成库文件名称 SHARED 单个文件或经set设置的文件合集)
add_libraries(生成库文件名称 SHARED 源文件1 源文件2 ...)

# 如下
add_library(planning SHARED ${SRC_LIST_LIB})
# 通过以上命令生成文件名字如下
# libplanning.so
将.cpp/.c/.cc源文件集合编译生成二进制可执行文件
# 将.cpp/.c/.cc文件生成可执行文件
add_executable(生成可执行文件名称 源文件1 源文件2 ...)
add_executable(生成可执行文件名称 单个文件或经set设置的文件合集)

add_executable(planning planing.cpp localplanning.cpp globleplanning.cpp)
add_executable(planning ${SRC_FILES})

查找指定依赖库文件

# 查找命令, 都是在 CMake 中用于查找外部库或包的命令
find_library()
find_path()
find_package()

find_library() 用于查找库文件的路径,find_path() 用于查找头文件或文件夹的路径,而 find_package() 则用于查找和导入外部包的配置信息。

  • find_library()

# 用于在系统或用户指定的路径 /path/to/custom/lib 中查找指定的库文件 libmylibrary.so,并将结果保存在变量 MY_LIBRARY中
# 通常用于查找并指定需要链接的库文件的路径,以便在后续的链接阶段使用
# NAMES 可选参数
find_library(MY_LIBRARY NAMES mylibrary PATHS /path/to/custom/lib)

# 使用 MY_LIBRARY 作为默认的库文件名称进行查找,使用默认的系统搜索路径 /usr/lib, /usr/local/lib, /lib(/lib待验证)
find_library(MY_LIBRARY)
  • find_path()

# 用于在系统或用户指定的路径 /path/to/include1 /path/to/include2 中查找指定的头文件 myheader.h(或文件夹),并将结果保存在变量HEADER_PATH中
#  通常用于查找所需的头文件路径,以便在编译阶段使用
find_path(HEADER_PATH myheader.h PATHS /path/to/include1 /path/to/include2)

实际开发过程中,经常不可避免会使用到第三方开源库,这些开源库可能是通过apt-get install命令自动安装到系统目录中,也可能是由我们自己下载库的源码然后通过编译安装到指定目录或者默认目录下的。

不管哪种方式安装的库文件,如果我们需要自己的项目中使用这些库,首先面临的第一个问题就是如何找到这些库。所谓“找到”这些库,其实是根据我们的需要找到指定版本的库头文件包含路径、链接库路径等,从而能够满足我们开发项目的编译链接需要。

# 用于查找和加载外部依赖的 CMake Package(一般是由第三方提供的模块化 CMake 配置)
# 用于查找和导入外部包的配置信息,可以指定所需的版本和组件。它会尝试找到指定的包,并设置相关的变量和模块以供后续使用
# 命令需要根据不同的包和系统环境进行配置,因为每个包的 CMake 配置可能会有所不同。需要确保已正确安装了要查找的包,并且 CMake 能够找到该包的配置文件或模块文件。有些包可能需要设置额外的路径或环境变量来帮助 CMake 找到它们。

find_package命令有两种工作模式,这两种工作模式的不同决定了其搜包路径的不同:

  • Module模式

find_package命令基础工作模式(Basic Signature),也是默认工作模式。

# Module模式基本语法
find_package(<package_name> [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [COMPONENTS <component1> <component2> ...])
  • <package_name> 必填参数;需要查找的包名,注意大小写。例如 OpenCVBoost

  • version 可选参数;指定所需的版本号

    常见选项包括:

  • EXACT 可选参数;要求找到的包的版本与指定的版本完全匹配,必须完全匹配的版本而不是兼容版本

  • QUIET 可选参数; 静默模式,不会显示详细的查找信息,表示如果查找失败,不会在屏幕进行输出

  • REQUIRED 可选参数;要求找到指定的包,如果找不到会产生错误,停掉整个CMake。而如果不指定REQUIRED则CMake会继续执行,必须检查找到的包的版本是否和version兼容

  • COMPONENTS 可选字段; 指定要加载的包的组件,如果有任何一个找不到就算失败

  • MODULE 可选字段;“如果Module模式查找失败则回退到Config模式进行查找”,但是假如设定了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不回落到Config模式查找。

# Module模式下是要查找到名为 Find<package_Name>.cmake 的配置文件,这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。Module 模式搜索这个文件的路径有两个:我们指定的CMAKE_MODULE_PATH的所在目录和CMake安装路径下的share/cmake-<version>/Modules目录
# 搜包路径依次为:
CMAKE_MODULE_PATH
CMAKE_ROOT
# 先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在CMake安装目录(即CMAKE_ROOT变量)下的Modules目录下(通常为/usr/share/cmake-3.16/Modules,3.16是我的CMake版本)查找。这两个变量可以在CMakeLists.txt文件中打印查看具体内容
message(STATUS "CMAKE_MODULE_PATH = ${CMAKE_MODULE_PATH}")
message(STATUS "CMAKE_ROOT = ${CMAKE_ROOT}")
# 其中CMAKE_MODULE_PATH默认为空,可以利用set命令赋值,我们来指定
# 在安装CMake时,CMake为我们提供了很多开发库的FindXXX.cmake模块文件,可以通过命令查询
cmake --help-module-list | grep -E ^Find
# 案例一:
# 例如,要查找和导入 OpenCV 库,可以使用以下命令
# 指定了要查找的 OpenCV 版本为 4.2.0,并且要求加载 core 和 imgproc 两个组件

find_package(OpenCV 4.2.0 REQUIRED COMPONENTS core imgproc)
# 指定了 OpenCV 的版本号为 4.2.0,并且使用 REQUIRED 标记,表示至少需要找到 4.2.0 版本的 OpenCV。如果找到的版本低于 4.2.0,将产生错误。
# OpenCV_DIR配置文件所在路径
message(STATUS "OpenCV_DIR = ${OpenCV_DIR}")
message(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")
message(STATUS "OpenCV_LIBS = ${OpenCV_LIBS}")

include_directories(${OPENCV_INCLUDE_DIRS}) 
add_executable(opencv_test opencv_test.cpp)  
target_link_libraries(opencv_test ${OpenCV_LIBS})

# find_package() 命令在查找和导入包后,系统会设置一些相关的变量
# 如 <package_name>_FOUND、<package_name>_VERSION、<package_name>_INCLUDES、<package_name>_INCLUDE_DIRS、<package_name>_LIBRARY、<package_name>_LIBRARIES、<package_name>_LIBS 等,可以在后续的 CMake 脚本中使用这些变量。
# 可以看到在执行find_package(OpenCV 4.2.0 REQUIRED COMPONENTS core imgproc)命令后,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中的库文件,而不会出现类似未定义的引用这样的链接错误。
# 案例二:
# 以curl库为例,假设我们项目需要引入这个库,从网站中请求网页到本地,我们看到官方已经定义好了FindCURL.cmake。所以我们在CMakeLists.txt中可以直接用find_pakcage进行引用
# 对于系统预定义的 Find<LibaryName>.cmake 模块,使用方法一般如下

find_package(CURL)
add_executable(curltest curltest.cc)
if(CURL_FOUND)
    target_include_directories(clib PRIVATE ${CURL_INCLUDE_DIR})
    target_link_libraries(curltest ${CURL_LIBRARY})
else(CURL_FOUND)
    message(FATAL_ERROR ”CURL library not found”)
endif(CURL_FOUND)

# 可以通过<LibaryName>_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭 某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。如果<LibaryName>_FOUND 为真,则将<LibaryName>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES
# 案例三:(经典案例,查找指定安装路径下的版本,如多版本protobuf3.6.1)

# 定义查找路径
set(Protobuf_PREFIX_PATH "/usr/protobuf")
# 添加到 CMAKE_PREFIX_PATH
list(APPEND CMAKE_PREFIX_PATH ${Protobuf_PREFIX_PATH})
# 查找 Protobuf
find_package(Protobuf REQUIRED)
message(STATUS ${Protobuf_INCLUDE_DIRS})

# 
list(APPEND CMAKE_PREFIX_PATH "/usr/protobuf")
find_package(Protobuf CONFIG)
  • Config模式

find_package命令高级工作模式(Full Signature)。 只有在find_package()中指定CONFIGNO_MODULE等关键字,或者Module模式查找失败后才会进入到Config模式。

相比于Module模式,Config模式的参数更多,也更复杂,但实际在使用过程中我们并不会用到所有参数,大部分参数都是可选的,我们只需要掌握基本的参数用法即可。

# 其中具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成
# 两种模式看起来似乎差不多,不过CMake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会找/usr/local/lib/cmake/XXX/中的XXXConfig.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
# 具体查找顺序为:
# 1、名为<PackageName>_DIR的CMake变量或环境变量路径默认为空。
# 这个路径是非根目录路径,需要指定到<PackageName>Config.cmake文件所在目录才能找到。
# 2、名为CMAKE_PREFIX_PATH、CMAKE_FRAMEWORK_PATH、CMAKE_APPBUNDLE_PATH的CMake变量或环境变量路径根目录,默认都为空。
#注意如果你电脑中安装了ROS并配置好之后,你在终端执行echo $CMAKE_PREFIX_PATH会发现ROS会将CMAKE_PREFIX_PATH这个变量设置为ROS中的库的路径/opt/ros/noetic,意思是会首先查找ROS安装的库,如果恰好你在ROS中安装了OpenCV库,就会发现首先找到的是ROS中的OpenCV,而不是你自己安装到系统中的OpenCV。
# 3、PATH环境变量路径根目录,默认为系统环境PATH环境变量值。
# 其实这个路径才是Config模式大部分情况下能够查找到安装到系统中各种库的原因。
# 这个路径的查找规则为:遍历PATH环境变量中的各路径,如果该路径如果以bin或sbin结尾,则自动回退到上一级目录得到根目录。
# 可以在find_package()前设定_DIR,指向包含config.cmake的目录。
# 先设定_ROOT,再设定_DIR,最后find_package();并且两个都能找到包,则_DIR起作用
set(OpenCV_ROOT "/home/nio/3rdparty/opencv-4.4.0/build")
set(OpenCV_DIR "/home/nio/3rdparty/opencv-4.4.0/build")
  • 包含其他cmake文件
include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径
  • pkgconfig详解

ref:

在CMake中优雅使用pkg-config - 知乎 (zhihu.com)

在使用 CMake 作为项目构建工具时,有一些库并没有提供 cmake 文件,往往提供的是 pkg-config.pc 文件,虽然可以在 cmake 中用 include_directorieslink_directories 来手动指定查找目录,但这样写并不能保证跨平台,甚至同一个库在不同Linux发行版中的位置也不一样,这个时候最好的解决方法就是能够在 cmake 中(优雅地)使用pkg-config提供的信息。

PkgConfig是一个用于管理和查询已安装软件包的工具。

一旦使用了find_package(PkgConfig REQUIRED),您就可以使用pkg_check_modules命令来查询特定软件包的信息。

find_package(Eigen3 REQUIRED)
pkg_check_modules(Eigen3 REQUIRED eigen3)
# 获取Eigen3的头文件路径
include_directories(${Eigen3_INCLUDE_DIRS})
# 获取Eigen3的库路径
link_directories(${Eigen3_LIBRARY_DIRS})
# 添加您的目标
add_executable(your_target_name your_source_files)
# 链接Eigen3库
target_link_libraries(your_target_name ${Eigen3_LIBRARIES})


# 但特殊,Eigen是一个头文件库(只有头文件,没有库文件),不需要显式链接库文件。只需包含Eigen的头文件路径即可使用其功能。
find_package(Eigen3 REQUIRED)
include_directories(${Eigen3_INCLUDE_DIRS})
# 添加您的目标
add_executable(your_target_name your_source_files)
# 案例一:
# 拿FFmpeg举例,这是一个纯C库,并且没有提供cmake配置文件,接下来我们要在 cmake 项目中使用 ffmpeg;
sensizlik@LoU:~$pkg-config --list-all | grep libav
libavformat                    libavformat - FFmpeg container format library
libavutil                      libavutil - FFmpeg utility library
libavcodec                     libavcodec - FFmpeg codec library
# 接下来我们在 CMakeLists.txt 中的相关位置添加如下语句:
find_package(PkgConfig REQUIRED)

pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil)

target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::ffmpeg)
# 如果以IMPORTED_TARGET形式导入,就不需要target_include_directories

# 案例二:查找并链接ZeroMQ库

# 法一:最快捷方式 通过使用 -l 选项指定要链接的库。在这种情况下,-lzmq 表示链接名为 "zmq" 的库。一旦找到该库文件,链接器将将其与目标程序进行链接,以使目标程序能够使用 ZeroMQ 库提供的功能。
# 链接器会在系统的默认库搜索路径或您指定的库路径中查找 libzmq.so
# 默认库搜索路径: /usr/lib /usr/local/lib /lib 环境变量 LD_LIBRARY_PATH 指定的路径,如/usr/lib/x86_64-linux-gnu
target_link_libraries(planning -lzmq)
# 先查询libzmq3-dev的安装路径,运行命令将列出 libzmq3-dev 软件包中包含的文件和安装路径。在列出的文件中,通常会包含库文件的路径(以 libzmq.so
dpkg-query -L libzmq3-dev
ls /usr/lib/libzmq*
# 法二: find_package()搭配pkg_check_modules()
find_package(PkgConfig REQUIRED)
pkg_check_modules(ZMQ REQUIRED libzmq)Zzz


对add_library或add_executable生成的目标库文件或者目标可执行二进制文件进行相关库链接操作

# 链接命令
link_directions()
target_link_libraries()
# 记住唯一一种就行,target_link_libraries万能链接,要写在add_executable之后,作用于指定的目标

target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)
------------------------------------
#方法一:通过绝对路径指定链接库的位置
# /path/to/custom/lib/libmylibrary.so 是要链接的库文件的绝对路径
target_link_libraries(MyExecutable PRIVATE /path/to/custom/lib/libmylibrary.so)

------------------------------------
#方法二:通过相对路径指定链接库的位置
# ${CMAKE_CURRENT_SOURCE_DIR}/libs/mylibrary.lib 是相对于当前源代码目录的相对路径,指定了要链接的库文件位置
# # 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
# mylibrary.lib==libmylibrary.so
target_link_libraries(MyExecutable PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libs/mylibrary.lib)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)
# 指定链接多个库
target_link_libraries(demo
    ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
    boost_system.a
    boost_thread
    pthread
    ${OpenCV_LIBS}
    )

------------------------------------
方法三(比较常用):使用自定义变量指定链接库的位置
set(MY_LIBRARY_PATH /path/to/custom/lib)
target_link_libraries(MyExecutable PRIVATE ${MY_LIBRARY_PATH}/libmylibrary.so)

------------------------------------
#方法四:使用 find_library() 命令查找链接库并且进行链接接操作
# find_library() 命令用于在指定的路径 /path/to/custom/lib 中查找名为 mylibrary 的库文件libmylibrary.so,并将结果保存在变量 MY_LIBRARY 中。然后,可以使用该变量来链接库。
find_library(MY_LIBRARY NAMES mylibrary PATHS /path/to/custom/lib)
target_link_libraries(MyExecutable PRIVATE ${MY_LIBRARY})

# 多个.so文件处理 (未验证@@@!!!)
file(GLOB LIBS_SO
	lib/*.so
	lib/*.a
)
target_link_libraries(demo ${LIBS_SO})

link_librariestarget_link_libraries 两个命令的区别

  • 作用对象不同:

    • link_libraries 命令用于设置全局链接库的列表。该命令会将指定的库链接到所有的目标(target)中,包括可执行文件和库文件。

    • target_link_libraries 命令用于将指定的库链接到特定的目标(target)中,可以是可执行文件或库文件。

  • 使用方式不同

    • link_libraries 命令的语法是直接列出需要链接的库的名称,多个库之间用空格分隔。

    link_libraries(library1 library2)
    • target_link_libraries 命令的语法是将需要链接的库作为参数传递给目标(target)。

    target_link_libraries(MyExecutable PRIVATE library1 library2)

    link_libraries 更适用于一次性链接多个库,并且不需要精细的目标控制的情况。它适用于简单的项目或需要快速链接库的情况。

通常情况下,建议使用 target_link_libraries 命令,因为它提供了更精确的控制,并且仅作用于指定的目标。


输出/打印信息

message(打印内容)
message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败

STATUS 是打印 -- 用来区分这是完整运行一条语句后的分割

find_package(Eigen3 REQUIRED)
message(STATUS "Eigen3_INCLUDE_DIR = ${EIGEN3_INCLUDE_DIR}")
# cmake .. 
# 加了STATUS
-- Eigen3_INCLUDE_DIR = /usr/include/eigen3
# 没加STATUS
Eigen3_INCLUDE_DIR = /usr/include/eigen3

添加编译选项

# 添加编译选项(add_definitions的功能和C/C++中的#define是一样的)
add_definitions(编译选项)

# 添加编译选项,包括使用C++11标准、启用调试信息和生成位置无关代码
add_compile_options(-std=c++11 -g -fPIC)

添加GDB调试信息

CMake I 指定构建类型Debug/Release


# 调试信息
set(CMAKE_CXX_FLAGS "-g")
# 开启所有警告,编译器将会显示一些潜在的问题或错误的警告信息
set(CMAKE_CXX_FLAGS "-Wall")
# 调试包不优化,-O0: 禁用优化,即编译器不会尝试对代码进行优化,有助于在调试时更好地理解程序的行为
set(CMAKE_CXX_FLAGS_DEBUG   "-O0" )
# release包优化
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG " )


# 支持gdb的调试
SET(CMAKE_BUILD_TYPE "Debug")
#  -g2 生成较多的调试信息,-ggdb 用于调试程序,生成针对GDB调试器的额外调试信息
# $ENV{CXXFLAGS}: 这是一个环境变量,表示在运行CMake时指定的附加C++编译选项。这样做可以确保在构建过程中保留用户设置的其他C++编译选项
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
# -O3: 开启较高级别的优化,编译器会尝试对代码进行更强的优化,以获得更好的性能
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

添加cmake执行编译的子目录

# 编译子文件夹的CMakeLists.txt
add_subdirectory(子文件夹名称)
add_subdirectory(${PROJECT_SOURCE_DIR}/src/base)

条件控制

FOREACH ENDFOREACH

附录:共有变量名

变量含义
PROJECT_SOURCE_DIR使用cmake命令后紧跟的目标,工程根目录(即顶层CMakeLists源码所在目录)
CMAKE_SOURCE_DIR等价于PROJECT_SOURCE_DIR
PROJECT_NAME返回通过PROJECT指令定义的项目名称
PROJECT_BINARY_DIR执行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build
CMAKE_BINARY_DIR等价于PROJECT_BINARY_DIR
CMAKE_INCLUDE_PATH系统环境变量,非cmake变量
CMAKE_LIBRARY_PATH系统环境变量,非cmake变量
CMAKE_CURRENT_SOURCE_DIR当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIRtarget编译目录(使用add_subdirectory(src bin)可以更改此变量的值 ,set(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对此变量有影响,只是改变了最终目标文件的存储路径)
CMAKE_CURRENT_LIST_FILE输出调用这个变量的CMakeLists.txt的完整路径
CMAKE_CURRENT_LIST_LINE输出这个变量所在的行
CMAKE_MODULE_PATH定义自己的cmake模块所在的路径(这个变量用于定义自己的cmake模块所在的路径,如果你的工程比较复杂,有可能自己编写一些cmake模块,比如SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块)
EXECUTABLE_OUTPUT_PATH重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH重新定义目标链接库文件的存放位置
CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS用来控制IF ELSE语句的书写方式
CMAKE_MAJOR_VERSIONmake主版本号,如3.4.1中3
CMAKE_MINOR_VERSIONcmake次版本号,如3.4.1中的4
CMAKE_PATCH_VERSIONcmake补丁等级,如3.4.1中的1
CMAKE_SYSTEM操作系统名称,包括版本名,如Linux-2.6.22
CAMKE_SYSTEM_NAME操作系统名称,不包括版本名,如Linux
CMAKE_SYSTEM_VERSION操作系统版本号,如2.6.22
CMAKE_SYSTEM_PROCESSOR电脑处理器名称,如i686
 

Study Reference

CMakeLists.txt 语法介绍与实例演练

C语言-Cmake-CMakeLists.txt教程

Linux下CMake简明教程

cmake使用教程(实操版)

CMakeLists.txt的超傻瓜手把手教程(附实例源码)

  • 60
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SensizliKLoU

感谢您的慷慨支持和鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值