CMake入门
CMake入门
1.什么是CMake?
CMake 是个一个开源的跨平台自动化建构系统,用来管理软件建置的程序,并不依赖于某特定编译器,并可支持多层目录、多个应用程序与多个函数库。
CMake 通过使用简单的配置文件 CMakeLists.txt,自动生成不同平台的构建文件(如 Makefile、Ninja 构建文件、Visual Studio 工程文件等),简化了项目的编译和构建过程。
CMake 本身不是构建工具,而是生成构建系统的工具,它生成的构建系统可以使用不同的编译器和工具链。
2.CMake的配置
我懒得手把手教你了,建议去查官方文档,实在不行csdn总会吧~
提示:Windows可以使用gnumake来make生成好的Makefile文件。
建议:WSL
3.CMake的简单使用
简单上手
最基础的CMake项目是由单个源代码文件构建的可执行文件。对于这样简单的项目,只需要一个包含三个命令的CMakeLists.txt文件即可。CMakeLists.txt是CMake标准配置文件,CMake工具会在该文件中读取和执行命令以构建项目,并且需要注意文件名的大小写。
cmake_minimum_required(VERSION 3.10)
project(test)
add_executable(test main.cpp)
任何项目的最顶层的CMakeLists.txt,最好首先使用cmake_minimum_required()命令指定一个最小CMake版本(非必须)。这能够确保下面的CMake函数能够以兼容版本的CMake运行。
为了启动一个项目,我们使用 project() 命令来设置项目名称。这个调用是每个项目都必须的,并且应该在cmake_minimum_required() 之后尽快调用。
最后,add_executable()命令告诉CMake使用指定的源代码文件创建一个可执行文件。
CMake中常用的系统变量
首先我们得介绍一下CMake中的一些常用的系统变量,后面代码演示时会用到。
CMAKE_PROJECT_X系列:
CMAKE_PROJECT_NAME
当前顶级项目的名称(project()定义的名字)。CMAKE_PROJECT_VERSION
项目的主版本号(由project(<name> VERSION ...)定义)。CMAKE_PROJECT_DESCRIPTION
项目的描述信息(由project(<name> DESCRIPTION ...)定义)。
CMAKE_X_DIR系列:
-
CMAKE_BINARY_DIR
表示项目的顶层构建目录——运行cmake命令时的目录,如:mkdir build cd build cmake ../project那么${CMAKE_BINARY_DIR}就是build/目录的绝对路径。
-
CMAKE_SOURCE_DIR
表示项目的顶层源码目录。例如,对于目录结构:/project ├── CMakeLists.txt # 顶层 CMakeLists.txt ├── src/ │ ├── CMakeLists.txt # src 子目录的 CMakeLists.txt │ ├── main.cpp └── include/ | └── mylib.h |如果你在
/project/src目录中执行cmake,CMAKE_SOURCE_DIR会指向/project(即顶层源码目录),而不管你从哪个子目录运行 CMake。 -
CMAKE_CURRENT_BINARY_DIR
当前CMakeLists.txt所在的构建目录。如上个示例,这是再在/project/src目录中执行cmake,CMAKE_CURRENT_BINARY_DIR就会指向/project/build/src(当前子目录的构建目录)。 -
CMAKE_CURRENT_SOURCE_DIR
CMAKE_CURRENT_SOURCE_DIR:指向 当前CMakeLists.txt文件所在的源代码目录,即你在CMake中处理的当前目录的路径。如果你在顶层CMakeLists.txt文件中,CMAKE_CURRENT_SOURCE_DIR就等于CMAKE_SOURCE_DIR,即项目的根目录。如果你在一个子目录(比如src)的CMakeLists.txt文件中,CMAKE_CURRENT_SOURCE_DIR就是该子目录的路径。
目标与文件相关变量:
-
CMAKE_RUNTIME_OUTPUT_DIRECTORY
可执行文件的输出目录。 -
CMAKE_LIBRARY_OUTPUT_DIRECTORY
动态库的输出目录。 -
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
静态库的输出目录。 -
CMAKE_SOURCE_FILES
项目中源文件的列表。 -
EXECUTABLE_OUTPUT_PATH与CMAKE_RUNTIME_OUTPUT_DIRECTORY相比范围更小,更简单地指定生成的可执行文件的输出目录。作用于全局项目中所有可执行文件,但不影响动态库或静态库的输出路径,笔者更建议使用CMAKE_RUNTIME_OUTPUT_DIRECTORY。
CMake中也可以自定义变量,在此不做讨论。
多个源文件
当我们的项目由多个源文件构成的时候,我们需要同时编译这些文件。
add_executable(test test1.cpp test2.cpp test3.cpp)
当然可以一个一个手动指定,但难以解决很多文件时的情况。这时我们可以使用cmake中的aux_source_directory命令。
aux_source_directory(dir VAR)
它的作用是把/dir目录中的所有源文件都存储在变量VAR中。这样的话我们就可以把CMakeLists.txt这样优化:
cmake_minimum_required(VERSION 3.10) #指定最低cmake版本
project(test CXX) #项目开始,指定名字与开发语言
aux_source_directory(. SRC_LIST) #搜索源码文件并存储到SRC_LIST中
add_executable(test ${SRC_LIST}) #添加要生成的可执行文件test,依赖SRC_LIST
注意,CMake中变量的使用要加上大括号,如${VAR_NAME}。
头文件
首先来看为全局设置头文件路径的情况(简单项目)。
对于集中的头文件,CMake提供了一个很方便的函数:
include_directories(dir)
它的作用是去/dir目录下寻找源文件,相当于gcc命令中的gcc -ldir(是的,-l后面没有空格)。而且还可以同时添加在不同文件夹中的源文件,如:
include_directoreis(./include1 ./include2)
有时我们想为特定目标(而不是向上面一样对全局文件操作)添加头文件路径,我们就可以使用target_include_directories命令。
target_include_directories(target_name PRIVATE include)
这表示为target_name添加头文件路径。
其中:PRIVATE表示仅作用域该目标,**PUBLIC**表示该目标和其依赖的目标都是用该路径,INTERFACE则表示只有依赖的目标使用该路径。
【注】:什么是“依赖”?
在 CMake 中,“依赖的目标”是指通过目标之间的链接关系显式依赖其他目标的构建对象。例如,库之间的链接或目标之间的关系会创建这种依赖。
PUBLIC 和 INTERFACE 的主要差别是:
PUBLIC:当前目标和所有依赖的目标都能使用头文件路径。INTERFACE:只有依赖的目标能使用,当前目标不能使用。
Set命令
CMake中的Set命令可以用来定义变量并设置变量的值。它是CMake脚本中最常用的命令之一。
基本用法如下所示:
set(<variable> <value>... [CACHE <type> <docstring> [FORCE]])
<variable>:要设置的变量名。<value>:变量的值,可以是单个值或多个值。CACHE:将变量存储在缓存中(即跨多次运行cmake命令时保存)。<type>:缓存变量的类型,如STRING、BOOL、FILEPATH等。<docstring>:为缓存变量提供的描述信息。FORCE:强制更新缓存变量的值,即使该变量已经存在。
我们通常可以用来设置目标文件的输出目录:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
生成库文件
在 CMake 中,可以通过添加特定目标(add_library)来生成库文件。库文件可以是静态库(.a 或 .lib)或者动态库(.so、.dll 或 .dylib),取决于你指定的库类型。
我们可以使用add_library命令来生成库文件。
add_library(target_name [STATIC | SHARED | MODULE] source1.cpp source2.cpp ...)
其中STATIC代表静态库,SHARED代表动态库,MODULE代表模块库,模块库在此不做探讨。
例如,我们可以:
cmake_minimum_required(VERSION 3.10)
project(MyStaticLibrary)
aux_source_directory(src SRC_LIB_LIST)
add_library(my_static_lib STATIC ${SRC_LIB_LIST})
target_include_directories(my_static_lib PUBLIC ${PROJECT_SOURCE_DIR}/indlue)
#指定输出路径(可选)
set_target_properties(mt_static_lib PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
生成的静态库文件可能是:
Linux: libmy_static_lib.a
Windows: my_static_lib.lib
这里再讲讲set_target_properties命令:
set_target_properties(target1 [target2 ...] PROPERTIES prop1 value1 [prop2 value2 ...])
参数解析:
target1 [target2 ...]: 要设置属性的目标名,可以是通过 add_library 或 add_executable 创建的目标。
PROPERTIES: 关键字,标明接下来是属性和值。
prop1 value1: 指定要设置的属性及其对应的值。
常见的属性及作用:
ARCHIVE_OUTPUT_DIRECTORY: 设置静态库(.a 或 .lib)的输出目录。
LIBRARY_OUTPUT_DIRECTORY: 设置动态库(.so、.dll 或 .dylib)的输出目录。
RUNTIME_OUTPUT_DIRECTORY: 设置可执行文件的输出目录。
OUTPUT_NAME: 更改生成的目标文件名。
set_target_properties(mt_static_lib PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
所以这句就代表设置静态库的输出目录为构建目录下的lib文件夹。
回归正题,我们再来生成一下动态库,步骤基本相似:
cmake_minimum_required(VERSION 3.10)
project(MySharedLibrary)
# 添加动态库目标
add_library(my_shared_lib SHARED src/lib.cpp src/lib.h)
# 指定输出路径(可选)
set_target_properties(my_shared_lib PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 添加头文件路径(可选)
target_include_directories(my_shared_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
生成的库文件可能是:
- Linux:
libmy_shared_lib.so - macOS:
libmy_shared_lib.dylib - Windows:
my_shared_lib.dll
链接库文件
target_link_libraries命令:
刚才我们生成了库文件,那么我们该如何链接这些文件呢?
通常,我们可以通过**target_link_libraries** 命令,将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
基本用法如下所示:
target_link_libraries(<target> [PRIVATE | PUBLIC | INTERFACE] <lib1> [<lib2> ...])
参数说明:
<target>:需要链接库的目标(如可执行文件或库)。- 作用域关键字(可选):
PRIVATE:仅当前目标可以使用这些链接的库。PUBLIC:当前目标和依赖于它的其他目标都能使用这些库。INTERFACE:仅依赖于当前目标的目标可使用这些库,而当前目标自身不使用。
<lib1>, <lib2>, ...:要链接的库
示例:
假设外部库路径为 /path/to/lib:
add_executable(my_program main.cpp)
link_directories(/path/to/lib) # 添加库路径
target_link_libraries(my_program PRIVATE mylib) # 链接外部库
或者直接用库文件的绝对路径:
target_link_libraries(my_program PRIVATE /path/to/lib/libmylib.a)
值得说明的是,动态库和静态库的使用方式类似,CMake会根据库的文件拓展名自动判断。
使用find_package查找外部库:
如果使用的是第三方库(例如 Boost、OpenCV),可以通过 find_package 自动查找库并链接。例如:
find_package(OpenCV REQUIRED)#查找OpenCV库,并设置相关的变量。REQUIRED表示必须找到该包,否则会导致配置失败并停止处理。
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(my_program main.cpp)
target_link_libraries(my_program PRIVATE ${OpenCV_LIBS})
需要知道的是,成功调用 find_package 后,CMake 会根据配置生成一系列变量,如:
1. 基本变量
这些变量表示是否成功找到包以及包的主要信息。
<Package>_FOUND- 表示是否成功找到包。
- 值为
TRUE或FALSE。 - 示例:
OpenCV_FOUND
<Package>_VERSION- 表示找到的包的版本号(如果支持版本查询)。
- 示例:
OpenCV_VERSION
<Package>_DIR- 表示找到包配置文件的路径。
- 示例:
OpenCV_DIR
2. 重要的路径变量
这些变量定义了找到的包的头文件、库文件、运行时文件的路径。
-
<Package>_INCLUDE_DIRS- 包的头文件路径,用于
target_include_directories。 - 示例:
OpenCV_INCLUDE_DIRS
- 包的头文件路径,用于
-
<Package>_LIBRARIES或<Package>_LIBS-
包的库文件路径,用于
target_link_libraries。 -
示例:
OpenCV_LIBS注:在
CMake中,<Package>_LIBS和<Package>_LIBRARIES都可能出现,但它们具体哪个会被生成取决于所使用的Find<Package>.cmake或<Package>Config.cmake文件的定义。一般来说:<Package>_LIBRARIES是更标准和推荐的变量名。<Package>_LIBS是某些第三方模块的自定义命名,通常见于较老或非官方的Find<Package>.cmake文件中。
-
-
<Package>_LIBRARY_DIRS- 包的库文件目录路径(有些包可能会提供,但不一定常见)。
- 示例:
OpenCV_LIBRARY_DIRS
-
<Package>_DEFINITIONS- 需要定义的编译器宏(通常用在
add_definitions()或target_compile_definitions()中)。 - 示例:
OpenCV_DEFINITIONS
- 需要定义的编译器宏(通常用在
find_library手动查找库文件:
find_library函数用于查找库,并把库的绝对路径和名称储存到第一个参数里。
find_library(var lib_name lib_path1 lib_path2)
参数说明:
- var:用于存储查找到的库
- lib_name:想要查找的库的名称,默认查找动态库,想要指定查找动态库或者静态库可以加后缀,如
lib.alib.so。 - lib_path:想要从哪个路径下查找库,可以指定多个路径。
然后我们就可以显式链接它啦:
target_link_libraries(target ${var})
这样,我们就把库lib_name连接到target可执行文件中了。
我们也可以这样:
# 查找库文件
find_library(MY_LIB NAMES mylibrary PATHS /path/to/libs) #其中NAMES、PATHS作为声明分隔参数,可以省略
# 如果找到该库,链接到目标
if(MY_LIB)
add_executable(my_program main.cpp)
target_link_libraries(my_program ${MY_LIB})
else()
message(WARNING "Library 'mylibrary' not found!")
endif()
注:find_package与find_library的区别:
-
find_library:专门用于查找一个特定的库文件(如静态库或动态库),返回库的路径。通常指定库的名称,不涉及头文件或其他依赖项。与find_package相比,find_library十分简单粗暴:在Linux中,默认就会在/usr/lib和/usr/local/lib目录下寻找目标包,而不需要CMake配置文件。 -
find_package:用于查找和配置整个软件包或库,通常一个软件包可能包含一个或多个库、头文件和其他资源。它可以自动处理依赖关系并提供可用的导出变量。但是使用find_package命令找库时,库里必须要用CMake配置文件,如SDL2_imageConfig.cmake,否则将无法找到。该文件要么由库的开发者提供,要么可以手动编写FindSDL2_image.cmake完成同样的功能。
补充:
install命令
install命令用于指定在安装时运行的规则。
(End)