CMake入门

CMake入门

1.什么是CMake?

CMake 是个一个开源的跨平台自动化建构系统,用来管理软件建置的程序,并不依赖于某特定编译器,并可支持多层目录、多个应用程序与多个函数库。

CMake 通过使用简单的配置文件 CMakeLists.txt,自动生成不同平台的构建文件(如 MakefileNinja 构建文件、Visual Studio 工程文件等),简化了项目的编译和构建过程。

CMake 本身不是构建工具,而是生成构建系统的工具,它生成的构建系统可以使用不同的编译器和工具链。

2.CMake的配置

我懒得手把手教你了,建议去查官方文档,实在不行csdn总会吧~

提示:Windows可以使用gnumake来make生成好的Makefile文件。

建议:WSL

3.CMake的简单使用

简单上手

最基础的CMake项目是由单个源代码文件构建的可执行文件。对于这样简单的项目,只需要一个包含三个命令的CMakeLists.txt文件即可。CMakeLists.txtCMake标准配置文件,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 目录中执行 cmakeCMAKE_SOURCE_DIR 会指向 /project(即顶层源码目录),而不管你从哪个子目录运行 CMake。

  • CMAKE_CURRENT_BINARY_DIR
    当前 CMakeLists.txt 所在的构建目录。如上个示例,这是再在 /project/src 目录中执行 cmakeCMAKE_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 中,“依赖的目标”是指通过目标之间的链接关系显式依赖其他目标的构建对象。例如,库之间的链接或目标之间的关系会创建这种依赖。

PUBLICINTERFACE 的主要差别是:

  • PUBLIC:当前目标和所有依赖的目标都能使用头文件路径。
  • INTERFACE:只有依赖的目标能使用,当前目标不能使用。

Set命令

CMake中的Set命令可以用来定义变量并设置变量的值。它是CMake脚本中最常用的命令之一。

基本用法如下所示:

set(<variable> <value>... [CACHE <type> <docstring> [FORCE]])
  1. <variable>:要设置的变量名。
  2. <value>:变量的值,可以是单个值或多个值。
  3. CACHE:将变量存储在缓存中(即跨多次运行 cmake 命令时保存)。
  4. <type>:缓存变量的类型,如 STRINGBOOLFILEPATH 等。
  5. <docstring>:为缓存变量提供的描述信息。
  6. 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_libraryadd_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> [PRIVATE | PUBLIC | INTERFACE] <lib1> [<lib2> ...])

参数说明:

  1. <target>:需要链接库的目标(如可执行文件或库)。
  2. 作用域关键字(可选):
    • PRIVATE:仅当前目标可以使用这些链接的库。
    • PUBLIC:当前目标和依赖于它的其他目标都能使用这些库。
    • INTERFACE:仅依赖于当前目标的目标可使用这些库,而当前目标自身不使用。
  3. <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
    • 表示是否成功找到包。
    • 值为 TRUEFALSE
    • 示例: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)

参数说明:

  1. var:用于存储查找到的库
  2. lib_name:想要查找的库的名称,默认查找动态库,想要指定查找动态库或者静态库可以加后缀,如lib.a lib.so
  3. 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_packagefind_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)


CMake入门
https://avisun.moe/archives/cmake_basic
作者
Sunster
发布于
2025年10月25日
许可协议