CMakeLists.txt快速入门

一:CMakeLists.txt的基本结构

编写CMakeLists.txt最常用的功能就是调用其他的.h头文件和.so/.a库文件,将.cpp/.c/.cc文件编译成可执行文件或者新的库文件

命令的官方网站:CMake Reference Documentation

最常用的命令如下:

# 本CMakeLists.txt的project名称
# 会自动创建两个变量,PROJECT_SOURCE_DIR和PROJECT_NAME
# ${PROJECT_SOURCE_DIR}:本CMakeLists.txt所在的文件夹路径
# ${PROJECT_NAME}:本CMakeLists.txt的project名称
project(xxx)

# 获取路径下所有的.cpp/.c/.cc文件,并赋值给变量中
aux_source_directory(路径 变量)

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

# 添加编译选项
add_definitions(编译选项)

# 打印消息
message(消息)

# 编译子文件夹的CMakeLists.txt
# 当运行到add_subdirectory这一句时,会先将子文件夹进行编译
add_subdirectory(子文件夹名称)

# 将.cpp/.c/.cc文件生成.a静态库
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
add_library(库文件名称 STATIC 文件)

# 将.cpp/.c/.cc文件生成可执行文件
add_executable(可执行文件名称 文件)

# 规定.h头文件路径
include_directories(路径)

# 规定.so/.a库文件路径
link_directories(路径)

# 对add_library或add_executable生成的文件进行链接操作
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)

通常一个CMakeLists.txt需按照下面的流程

project(xxx)                                          #必须

add_subdirectory(子文件夹名称)                         #父目录必须,子目录不必

add_library(库文件名称 STATIC 文件)                    #通常子目录(二选一)
add_executable(可执行文件名称 文件)                     #通常父目录(二选一)

include_directories(路径)                              #必须
link_directories(路径)                                 #必须

target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)       #必须

除了这些之外,就是些set变量的语句,if判断的语句,或者其他编译选项的语句,但基本结构都是这样的。

以上参考:【CMake】CMakeLists.txt的超傻瓜手把手教程(附实例源码)_cmakelists教程_Yngz_Miao的博客-CSDN博客

二:CmakeLists构建核心

一个项目最终要生成可执行文件并运行,核心主要有3点:

1.可执行文件运行的是main函数里面的内容。(add_executable)

2.编译时需要解析头文件。(include_directories)

3.可执行文件需要链接各种库文件。(target_link_libraries)

因此以下三个指令是CmakeLists的构建核心,其余指令都是用来辅助这3个指令的。

# 1.规定.h头文件路径
include_directories(${PROJECT_SOURCE_DIR})
# 2.生成可执行文件,test为可执行文件的名称
add_executable(test main.cpp)
# 3.为可执行文件链接库文件 libadd.so
target_link_libraries(test add)

总结:父目录和子目录中的CmakeLists.txt文件加在一起,必须指定整个项目中用到的全部头文件路径,必须给可执行文件链接整个项目中用到的全部库文件。

三:Cmake核心指令

include_directories、add_library、link_directories、target_link_libraries这几个命令需要搞懂其中的联系与区别:

1.在这里首先要注意的一点是头文件和库文件的区别:(编译和链接)

头文件:头文件本身只是包含了函数和类的声明,主要用于在编译时进行类型检查和符号解析。头文件不包含函数的定义,它们不会被直接链接到可执行文件中。

库文件:是一组已经编译好的目标文件(对象文件)的集合。库可以包含已经定义的函数、变量和其他符号的实现。链接库可以被链接到可执行文件中,使得可执行文件能够调用库中的函数和使用库中的资源。

2.知道了区别之后,就容易理解上面几个命名的区别

编译命令:include_directories

链接命令:add_library、link_directories、target_link_libraries

其中include_directories指令是用于指定头文件所在目录,以便在编译源文件时可以找到所需的头文件。

其余三个则是和库文件相关的操作指令:

add_library:这个命令用于创建一个内部库,并将源文件(.cc/.c文件)编译成目标文件。它可以创建静态库或动态库。这个命令用于编译阶段,将源文件编译为目标文件。

link_directories:指令是用于指定外部库文件所在目录并查找外部库文件。它告诉链接器在指定的目录中查找库文件。这是链接阶段的命令。

target_link_libraries:这个命令用于将库链接到目标(可执行文件或库)中。它指定了目标需要依赖的库。这是链接阶段的命令。

总结:add_library用于创建新的内部库,link_directories用于查找已有的外部库,target_link_libraries用于将库(内部库和外部库)链接到目标。

3.与之相结合的命令

find_package()find_library()是 CMake 中用于查找外部库的两个不同的命令。

(1)find_package():

它用于查找和定位已经安装在系统中的 CMake 包与之对应的配置文件(通常是Find<PackageName>.cmake),如果找到了包的配置文件,CMake 将加载该文件并设置相关变量,例如包含目录、库文件和其他配置选项。

例如,可以使用以下命令查找并加载 OpenCV 包:

find_package(OpenCV 3.4 REQUIRED)

3.4:也可以不加,当系统中有多个opencv版本时,加上表示寻找特定的版本。

REQUIRED:如果找不到指定的包,则停止构建过程并产生一个错误。使用 REQUIRED 选项可以将某个包标记为必需的,如果无法找到该包,CMake 将无法继续构建项目。

QUIET:用于指示 CMake 在查找包时不输出详细信息,只显示错误和警告信息。使用 QUIET 选项可以减少 CMake 输出的冗余信息,使输出更简洁。

使用 find_package() 命令找到并加载OpenCV包的配置文件后,会设置这几个变量供后续的 CMake 命令使用:

* OpenCV_FOUND:一个布尔变量,指示是否找到了指定的包。如果找到了,该变量值为 TRUE;否则,为 FALSE

* OpenCV_INCLUDE_DIRS:一个表示包含目录的路径的变量。它用于指示包的头文件所在的目录路径。

* OpenCV_LIBRARIES:一个表示库文件的路径或名称的变量。它用于指示包的库文件路径或库文件的名称。

* OpenCV_VERSION一个表示包的版本号的变量。它用于指示找到的包的版本。

后续的 CMake 命令使用:

include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(${OpenCV_LIBS})

(2)find_library()

它用于在系统中查找指定的库文件。与 find_package() 不同,它主要用于查找单个库文件,而不是整个包。在找到库文件后,它会设置一些变量以供后续的 CMake 命令使用。以下是 find_library() 命令返回的常见变量:

* <VAR>一个表示找到的库文件的完整路径或名称的变量。通常,您可以在命令中指定一个名为 <VAR> 的变量,来接收找到的库文件的路径。

find_library(VAR NAMES mylib)
if (VAR_NOTFOUND)
    message("Could not find mylib")
else()
    message("Found mylib at ${VAR}")
endif()

四:静态库和动态库区别

静态库、动态库和共享库是软件开发中常见的三种库文件形式,它们在编译、链接和运行时的行为和特性有所不同。

1. 静态库(Static Library):

   - 编译时:静态库是在编译时被链接到目标程序中的库文件。编译器将库的代码和数据复制到最终的可执行文件中。
   - 链接时:静态库的代码被完整地复制到目标程序中,使得目标程序成为一个独立的执行文件,不再依赖于原始的静态库文件。
   - 运行时:目标程序在运行时不需要额外的依赖,因为静态库的代码已经完全包含在目标程序中

2. 动态库(Dynamic Library)

   - 编译时:动态库在编译时并不会被复制到目标程序中,而是作为一个独立的文件存在。
   - 链接时:目标程序在链接时仅包含对动态库的引用,而不是完整的库代码。链接器记录了目标程序需要的动态库的信息。
   - 运行时:在运行时,目标程序加载动态库文件,并在内存中共享该库的代码和数据。多个程序可以共享同一个动态库,减少了内存的占用。

3. 共享库(Shared Library):

   - 共享库是动态库的一种常见形式,上述动态库的特性同样适用于共享库。
   - 共享库还可以被多个程序同时使用,这使得共享库更加灵活和可复用。

选择使用静态库、动态库或共享库取决于开发环境、项目需求和性能等因素。静态库适用于独立的、较小规模的项目,而动态库和共享库适用于大型项目或需要动态加载和共享的场景。