CMake基本入门手册

1、CMake概述

CMake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过 make 命令进行项目的构建),大多是IDE软件都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等,如果自己动手写 makefile,会发现,makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。
而 CMake 恰好能解决上述问题, 其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具,其编译流程如下图:
在这里插入图片描述

在这里插入图片描述

介绍完CMake的作用之后,再来总结一下它的优点:
跨平台
能够管理大型项目
简化编译构建过程和编译过程
可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能

2、CMake的使用

CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意

2.1、注释

行注释:使用#
块注释 使用#[[ ]]

#cmake 所需要的最小Cmake版本
cmake_minimum_required(VERSION 3.31.0)
#[[ 这是一个 CMakeLists.txt 文件。
这是一个块注释
这是一个 CMakeLists.txt 文件]]

2.2、双引号的使用

  • 对命令参数的影响
    针对没有空格的命令 加上双引号和没加双引号两者的区别不大,但是针对存在空格的命令参数如 program file 是不同
message("program file")    # 输出的program file
message(program file)    # 输出的programfile   中间的空格会被忽略掉
  • 引用变量的时候
set(MY_LIST Hello World China)
message(${MY_LIST})
输出:
HelloWorldChina
set(MY_LIST Hello World China)
message("${MY_LIST}")
输出:Hello;World;China

"${MY_LIST}"这种形式的时候,要让 CMake 把这个数组的所有元素当成一个整体,而不是分散的个体。
为了保持数组的含义,又提供一个整体的表达方式,CMake 就会用分号“;” 把这数组的多个元素连接起来

2.3、Cmake常用的命令

2.3.1 定义变量

  • 设置简单变量
    语法:
set(VARIABLE_NAME value)   #这会将 VARIABLE_NAME 设置为指定的 value。如果变量已经存在,则会覆盖其旧值。

eg:
set(MY_VARIABLE "Hello, World!")
  • 使用简单变量
${ VARIABLE_NAME}
  • 设置多个值 (列表)
#可以将多个值赋给一个变量,形成一个列表:
set(VARIABLE_NAME value1 value2 value3)

eg:
 set(SOURCES main.cpp utils.cpp helper.cpp) 

注意空格分隔的字符,如果使用带有空格的的字符串,这需要使用引号括起来

set(VARIABLE_NAME "This is a string with spaces")
  • 使用 set 追加值
set(VARIABLE_NAME ${VARIABLE_NAME} new_value)
  • 使用 list 命令来追加值,这样可以避免潜在的空格问题
set(SOURCES main.cpp)
list(APPEND SOURCES utils.cpp)
  • 设置缓存变量
    你可以将变量存储在 CMake 缓存中,这样它们可以在多次构建之间保持不变。缓存变量可以通过命令行进行修改。
set(VARIABLE_NAME value CACHE TYPE "Description")

set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build, options are: None Debug Release.")

TYPE 可以是以下几种之一:

  • STRING:普通字符串。
  • PATH:路径类型,通常用于文件或目录路径。
  • FILEPATH:文件路径类型。
  • BOOL:布尔类型,值为 ON 或 OFF。
  • INTERNAL:内部变量,不会显示在 CMake GUI 中。
    设置环境变量
    CMake 本身不直接操作环境变量,但你可以通过 set(ENV{VARIABLE_NAME} value) 来设置环境变量。 这种方式只会在 CMake 配置过程中生效,不会影响系统的环境变量。
    设置父作用域中的变量
    默认情况下,set 命令设置的变量只在当前作用域内可见。如果你想将变量设置为父作用域可见,可以使用 PARENT_SCOPE 选项。
function(my_function)
    set(MY_VARIABLE "Hello from function" PARENT_SCOPE)
endfunction()

my_function()
message(STATUS "MY_VARIABLE: ${MY_VARIABLE}")  # 输出: MY_VARIABLE: Hello from function

检查变量是否存在
使用 if 语句来检查变量是否存在:

if(DEFINED VARIABLE_NAME)
    message(STATUS "VARIABLE_NAME is defined")
else()
    message(STATUS "VARIABLE_NAME is not defined")
endif()
  • 设置变量为空
    变量设置为空字符串或未定义状态:
    设置变量为空
set(VARIABLE_NAME "")
  • 取消变量设置
unset(VARIABLE_NAME)
  • 设置变量的默认值
    可以使用 if(NOT DEFINED VARIABLE_NAME) 来检查变量是否已定义,并为其设置默认值:
if(NOT DEFINED MY_VARIABLE)
    set(MY_VARIABLE "default_value")
endif()
  • 使用 set 进行条件赋值
if(CONDITION)
    set(VARIABLE_NAME value1)
else()
    set(VARIABLE_NAME value2)
endif()

if(UNIX)
    set(OS_SPECIFIC_FLAGS "-Wall -Wextra")
elseif(WIN32)
    set(OS_SPECIFIC_FLAGS "/W4")
endif()
  • 使用 set 进行数学运算
    CMake 提供了 math(EXPR …) 和 math(EXPR …) 命令来进行数学运算,但你也可以使用 set 结合表达式来进行简单的数学运算
set(VARIABLE_NAME ${VARIABLE_NAME} + 1)

set(COUNT 0)
set(COUNT ${COUNT} + 1)
message(STATUS "COUNT: ${COUNT}")  # 输出: COUNT: 1
  • 使用 set 进行字符串拼接
    可以使用 ${} 语法进行字符串拼接:
set(VARIABLE_NAME "${PREFIX}_${SUFFIX}")

set(PREFIX "prefix")
set(SUFFIX "suffix")
set(FULL_NAME "${PREFIX}_${SUFFIX}")
message(STATUS "FULL_NAME: ${FULL_NAME}")  # 输出: FULL_NAME: prefix_suffix
  • 使用 set 进行路径操作
    可以使用 set 结合 file 命令进行路径操作,例如获取当前目录、拼接路径等
set(CURRENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(OUTPUT_DIR "${CURRENT_DIR}/build")

使用 set 进行多平台配置
可以根据不同的平台设置不同的变量值:

if(UNIX)
    set(OS_SPECIFIC_FLAGS "-Wall -Wextra")
elseif(WIN32)
    set(OS_SPECIFIC_FLAGS "/W4")
endif()
  • 使用 set设置 C++标准
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)

2.3.2 搜索文件

find_file 命令用于在指定路径中搜索文件,并将找到的文件路径存储在一个变量中。
基本用法

find_file(VARIABLE_NAME file_name [PATHS path1 path2 ...] [PATH_SUFFIXES suffix1 suffix2 ...])
- VARIABLE_NAME:存储找到的文件路径的变量。
- file_name:要搜索的文件名。
- PATHS:可选参数,指定搜索的路径列表。
- PATH_SUFFIXES:可选参数,指定搜索路径的子目录列表。

示例
假设你要搜索一个名为 config.h 的文件,可以在项目的根目录及其子目录中进行搜索:

find_file(CONFIG_FILE_PATH config.h
        PATHS ${CMAKE_SOURCE_DIR}
        PATH_SUFFIXES include)

如果找到了 config.h 文件,CONFIG_FILE_PATH 变量将包含该文件的完整路径;否则,它将为空
也可以通过CMAKE_INCLUDE_PATH 宏定义指定搜索的路径

  • 设置 CMAKE_INCLUDE_PATH 变量
set(CMAKE_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/src)
find_file(CONFIG_FILE_PATH config.h)

使用 file(GLOB …) 搜索多个文件
file(GLOB …) 命令用于根据通配符模式搜索多个文件,并将结果存储在一个列表中。

file(GLOB/GLOB_RECURSE VARIABLE_NAME [RELATIVE path] pattern1 pattern2 ...)

GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
VARIABLE_NAME:存储找到的文件路径列表的变量。
RELATIVE:可选参数,指定相对于某个路径的相对路径。
pattern1 pattern2 …:通配符模式,用于匹配文件名。

eg
file(GLOB SOURCES *.cpp)
file(GLOB HEADERS *.h)

获取相对于项目根目录的相对路径,可以使用
RELATIVE 选项
file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_SOURCE_DIR} *.cpp)

  • 使用 find_path 搜索路径
    find_path 命令用于搜索包含特定文件的目录,并将找到的目录路径存储在一个变量中。
    find_path(VARIABLE_NAME file_name [PATHS path1 path2 …] [PATH_SUFFIXES suffix1 suffix2 …])

VARIABLE_NAME:存储找到的目录路径的变量。
file_name:要搜索的文件名。
PATHS:可选参数,指定搜索的路径列表。
PATH_SUFFIXES:可选参数,指定搜索路径的子目录列表。

#[[如果找到了 config.h 文件,
CONFIG_INCLUDE_DIR 变量将包含该文件所在目录的路径;否则,它将为空]]

find_path(CONFIG_INCLUDE_DIR config.h
          PATHS /usr/include /opt/include
          PATH_SUFFIXES myproject)
  • 使用 find_library 搜索库文件
    find_library 命令用于搜索库文件(如 .a、.lib 或 .so 文件),并将找到的库文件路径存储在一个变量中
find_library(VARIABLE_NAME library_name [PATHS path1 path2 ...] [PATH_SUFFIXES suffix1 suffix2 ...])

VARIABLE_NAME:存储找到的库文件路径的变量。
library_name:要搜索的库文件名(不带扩展名)。
PATHS:可选参数,指定搜索的路径列表。
PATH_SUFFIXES:可选参数,指定搜索路径的子目录列表。

eg:
    find_library(MYLIB_LIBRARY mylib
             PATHS /usr/lib /opt/lib
             PATH_SUFFIXES myproject)
  • 使用 find_package 搜索外部包
    find_package 命令用于查找并导入外部包,并设置相关的变量。通常,find_package 会调用相应的
    Find.cmake 脚本来执行搜索操作。
find_package(PackageName [version] [REQUIRED] [COMPONENTS component1 component2 ...])

PackageName:要查找的包名称。
version:可选参数,指定所需的最低版本。
REQUIRED:可选参数,表示如果找不到包则终止配置过程。
COMPONENTS:可选参数,指定需要的组件。

查找 Qt5 并导入其 Widgets 组件
find_package(Qt5 COMPONENTS Widgets REQUIRED)
aux_source_directory 命令可以查找某个路径下的所有源文件
aux_source_directory(< dir > < variable >)

dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中

  • 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app  ${SRC_LIST})

2.3.3 包含头文件

使用 include_directories 添加头文件路径
include_directories 是 CMake 中最常用的命令之一,用于添加头文件的搜索路径。它会将指定的路径添加到编译器的
-I 选项中,使得编译器能够在这些路径中查找头文件。

include_directories([AFTER|BEFORE] [SYSTEM] path1 path2 ...)
AFTER 或 BEFORE
选项来指定是添加到列表的前面或者后面。
 SYSTEM 选项,会把指定目录当成系统的搜索目录。
path1 path2 ...:要添加的头文件路径列表。

eg:
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)

# 查找源文件
aux_source_directory(src SOURCES)

# 添加可执行文件
add_executable(MyExecutable ${SOURCES})

注意事项

  • include_directories 的作用范围是全局的,即它会影响所有后续定义的目标(如 add_executable 或 add_library)。如果你只想为特定目标添加头文件路径,可以考虑使用 target_include_directories。
  • 使用 target_include_directories 为特定目标添加头文件路径
    target_include_directories 是一个更现代和推荐的方式,用于为特定目标(如可执行文件或库)添加头文件路径。它提供了更好的控制和灵活性,尤其是当你有多个目标时
target_include_directories(target_name PRIVATE|PUBLIC|INTERFACE path1 path2 ...)

target_name:目标的名称(如 add_executable 或 add_library 定义的目标)。
PRIVATE|PUBLIC|INTERFACE:指定头文件路径的可见性:
PRIVATE:仅对当前目标可见。
PUBLIC:对当前目标及其依赖的目标可见。
INTERFACE:仅对依赖当前目标的目标可见


eg:
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 查找源文件
aux_source_directory(src SOURCES)

# 创建可执行文件
add_executable(MyExecutable ${SOURCES})

# 为 MyExecutable 添加头文件路径
target_include_directories(MyExecutable PRIVATE ${CMAKE_SOURCE_DIR}/include)

# 为 MyLibrary 添加头文件路径,并使其对依赖它的目标可见
target_include_directories(MyLibrary PUBLIC ${CMAKE_SOURCE_DIR}/include)

2.3.4 制作静态库

add_library(库名称 STATIC 源文件1 [源文件2] ...) 
#[[动态库]]
add_library(库名称 SHARED 源文件1 [源文件2] ...) 

2.3.5 包含库文件

link_libraries(<static lib> [<static lib>...])

用于设置全局链接库,这些库会链接到之后定义的所有目标上。
参数1:指定出要链接的静态库的名字
可以是全名 libxxx.a
也可以是掐头(lib)去尾(.a)之后的名字 xxx
参数2-N:要链接的其它静态库的名字
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来
link_directories()

# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)

target_link_libraries 是 CMake 中最常用的命令之一,用于将库链接到特定的目标(如可执行文件或库)。它提供了灵活的方式来管理依赖关系,并且可以处理静态库、动态库以及外部库

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

target_name:目标的名称(如 add_executable 或 add_library 定义的目标)。
该文件可能是一个源文件
该文件可能是一个动态库/静态库文件
该文件可能是一个可执行文件

PRIVATE|PUBLIC|INTERFACE:指定库的可见性:
PRIVATE:仅对当前目标可见。
PUBLIC:对当前目标及其依赖的目标可见。
INTERFACE:仅对依赖当前目标的目标可见。
library1 library2 ...:要链接的库列表。

2.3.6日志

message([SEND_ERROR | STATUS | WARNING | AUTHOR_WARNING | FATAL_ERROR | DEPRECATION] "message to display" ...)
- SEND_ERROR:输出一条错误信息,并将 CMake 配置过程标记为失败,但不会立即终止。
- STATUS:输出一条状态信息,通常用于提供构建过程中的进度或信息。
- WARNING:输出一条警告信息,提示用户可能存在潜在问题。
- AUTHOR_WARNING:类似于 WARNING,但专门用于 CMake 脚本作者之间的沟通,通常在开发过程中使用。
- FATAL_ERROR:输出一条致命错误信息,并立即终止 CMake 配置过程。
- DEPRECATION:输出一条弃用警告,提示用户某些功能或命令即将被移除。

2.3.7 list 相关操作

list 是 CMake 中用于操作列表(即字符串列表)的命令。语法主要如下:
list( […])

常用命令包括:

  • APPEND:向列表中添加元素。
  • LENGTH:获取列表的长度。
  • GET:获取指定索引的元素。
  • FIND:查找指定的元素。
  • INSERT:在指定位置插入元素。
  • REMOVE_ITEM:移除匹配的元素。
  • REMOVE_AT:移除指定索引的元素。
  • REMOVE_DUPLICATES:移除重复的元素。
  • REVERSE:反转列表顺序。
  • SORT:对列表进行排序。
  • JOIN:将列表连接成字符串。
  • TRANSFORM:对列表中的每个元素应用表达式。
  1. APPEND
    向列表中添加一个或多个元素。如果元素已经是列表的一部分,则不会重复添加。
    list(APPEND […])
eg:
set(MY_LIST "apple" "banana")
list(APPEND MY_LIST "orange" "grape")

# MY_LIST 现在是 "apple;banana;orange;grape"
  1. LENGTH
    获取列表的长度,并将结果存储在一个变量中
list(LENGTH <variable> <output_variable>)

set(MY_LIST "apple" "banana" "orange")
list(LENGTH MY_LIST LIST_LENGTH)

# LIST_LENGTH 现在是 3
  1. GET
    从列表中获取指定索引的元素,并将结果存储在一个变量中。索引从 0 开始。
list(GET <variable> <index> <output_variable>)
list(GET <variable> <index> <output_variable>)
  1. FIND
    在列表中查找指定的元素,并返回其索引。如果找不到该元素,则返回 -1。
    list(FIND <output_variable>)
    :要查询的列表变量。
    :要查找的元素。
    <output_variable>:存储找到的索引的变量。
eg
set(MY_LIST "apple" "banana" "orange")
list(FIND MY_LIST "banana" INDEX)

# INDEX 现在是 1
  1. INSERT
    在列表的指定位置插入一个或多个元素
    list(INSERT […])
    :要操作的列表变量。
    :插入位置的索引(从 0 开始)。
    …:要插入的元素。
set(MY_LIST "apple" "orange")
list(INSERT MY_LIST 1 "banana")

# MY_LIST 现在是 "apple;banana;orange"
  1. REMOVE_ITEM
    从列表中移除所有匹配指定值的元素
    list(REMOVE_ITEM […])
    :要操作的列表变量。
    …:要移除的元素。
eg
set(MY_LIST "apple" "banana" "orange" "banana")
list(REMOVE_ITEM MY_LIST "banana")

# MY_LIST 现在是 "apple;orange"
  1. REMOVE_AT
    从列表中移除指定索引的元素。
    list(REMOVE_AT …)
    :要操作的列表变量。
    …:要移除的元素索引(可以指定多个索引)。
set(MY_LIST "apple" "banana" "orange" "grape")
list(REMOVE_AT MY_LIST 1 3)

# MY_LIST 现在是 "apple;orange"
  1. REMOVE_DUPLICATES
    从列表中移除重复的元素,只保留第一次出现的元素。
    list(REMOVE_DUPLICATES )

set(MY_LIST “apple” “banana” “orange” “banana” “grape”)
list(REMOVE_DUPLICATES MY_LIST)

MY_LIST 现在是 “apple;banana;orange;grape”

  1. REVERSE
    反转列表中的元素顺序。
    list(REVERSE )
set(MY_LIST "apple" "banana" "orange")
list(REVERSE MY_LIST)

# MY_LIST 现在是 "orange;banana;apple"
  1. SORT
    对列表中的元素进行排序。默认按字典顺序排序,也可以指定自定义排序规则。
    list(SORT [COMPARE { comparespec } | ORDER { asc | desc }])

:要操作的列表变量。
COMPARE:指定比较规则(可选)。
ORDER:指定排序顺序(asc 表示升序,desc 表示降序,默认为 asc)。

s

et(MY_LIST "banana" "apple" "orange")
list(SORT MY_LIST)

# MY_LIST 现在是 "apple;banana;orange"

# 按降序排序
list(SORT MY_LIST ORDER desc)

# MY_LIST 现在是 "orange;banana;apple"
  1. JOIN
    将列表中的元素连接成一个字符串,并使用指定的分隔符分隔
    list(JOIN <output_variable>)
    :要操作的列表变量。
    :用于分隔元素的字符串。
    <output_variable>:存储连接后的字符串的变量
set(MY_LIST "apple" "banana" "orange")
list(JOIN MY_LIST ", " JOINED_STRING)

# JOINED_STRING 现在是 "apple, banana, orange"
  1. TRANSFORM
    对列表中的每个元素应用一个表达式,并生成一个新的列表。
    list(TRANSFORM [OUTPUT_VARIABLE <output_variable>])

:要操作的列表变量。
:要应用的表达式。
OUTPUT_VARIABLE:可选参数,指定输出变量。如果不指定,则直接修改原列表。

set(MY_LIST "apple" "banana" "orange")
list(TRANSFORM MY_LIST PREPEND "fruit:")

# MY_LIST 现在是 "fruit:apple;fruit:banana;fruit:orange"

2.3.8 宏定义

target_compile_definitions 定义宏
使用 target_compile_definitions 命令为特定的目标定义预处理宏。这比全局定义宏更加灵活。因为你可以为不同的目标定义不同的宏。

target_compile_definitions(target_name PRIVATE|PUBLIC|INTERFACE macro1 macro2...)

target_name:目标的名称(如 add_executable 或 add_library 定义的目标)。
PRIVATE|PUBLIC|INTERFACE:指定宏的可见性:
PRIVATE:仅对当前目标可见。
PUBLIC:对当前目标及其依赖的目标可见。
INTERFACE:仅对依赖当前目标的目标可见。
macro1 macro2...:要定义的宏。

 
MyExecutable 目标定义一个名为 MY_MACRO 的宏:
 
add_executable(MyExecutable src/main.cpp)
target_compile_definitions(MyExecutable PRIVATE MY_MACRO)
# 设置了宏 并且宏的值为1
target_compile_definitions(MyExecutable PRIVATE MY_MACRO=1)
add_definitions 全局定义宏
add_definitions 命令用于全局定义预处理宏。它会影响所有后续定义的目标
add_definitions(-Dmacro1 -Dmacro2=1 ...)

2.3.9 add_executable 可执行程序目标

add_executable(<target_name> [source1] [source2 ...])
  • <target_name>:可执行文件的目标名称。这个名称将用于后续的 CMake 命令(如 target_link_libraries、target_include_directories 等)。
  • [source1] [source2 …]:可选参数,表示用于构建该可执行文件的源文件列表。可以是 C、C++、汇编等语言的源文件。如果省略源文件列表,可以在稍后使用 target_sources 命令添加源文件。
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 查找 src 目录下的所有源文件
aux_source_directory(src SOURCES)

# 添加可执行文件,使用变量中的源文件列表
add_executable(MyExecutable ${SOURCES})

2.3.10 add_library

add_library 命令用于添加一个库文件目标,并设置目标所需的源文件

add_library(<target_name> [STATIC | SHARED | OBJECT] [source1] [source2 ...])
  • <target_name>:库的目标名称。这个名称将用于后续的 CMake 命令(如 target_link_libraries、target_include_directories 等)。
  • [STATIC | SHARED | OBJECT]:可选参数,指定库的类型:
  • STATIC:创建静态库(.a 或 .lib 文件)。静态库会被链接到最终的可执行文件中,生成的可执行文件是自包含的。
  • SHARED:创建动态库(.so 或 .dll 文件)。动态库在运行时加载,可以被多个程序共享。
  • OBJECT:创建对象库(.o 或 .obj 文件)。对象库是一组编译后的对象文件,但不会进行链接。通常用于模块化构建或与其他库组合使用。
  • [source1] [source2 …]:可选参数,表示用于构建该库的源文件列表。可以是 C、C++、汇编等语言的源文件。如果省略源文件列表,可以在稍后使用 target_sources 命令添加源文件。

2.3.11 add_subdirectory

add_subdirectory 是 CMake 中用于将一个子目录中的 CMake 项目纳入当前项目的命令。它允许你将项目拆分为多个子模块,每个子模块可以有自己的 CMakeLists.txt 文件,并且可以在主项目中统一管理这些子模块。通过 add_subdirectory,你可以实现模块化构建,使项目结构更加清晰、易于维护。
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

  • source_dir:必需参数,指定子项目的源代码目录。CMake 会在这个目录中查找 CMakeLists.txt 文件并执行其中的命令。
  • binary_dir:可选参数,指定生成文件(如目标文件、编译输出等)的存放目录。如果不指定,默认情况下生成文件会放在与 source_dir 相对的构建目录中。
  • EXCLUDE_FROM_ALL:可选参数,如果指定了这个选项,则该子目录中的目标不会被默认构建。只有在显式调用时才会构建这些目标。
    MyProject/
├── CMakeLists.txt
└── src/
    ├── CMakeLists.txt
    └── main.cpp
    
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加 src 子目录
add_subdirectory(src)
   
    
 #指定生成文件目录    
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加 src 子目录,并指定生成文件目录为 build/src
#在这种情况下,src 目录中的生成文件将会放在 build/src 目录中,而不是直接放在 
src 目录中。
add_subdirectory(src build/src) 

排除子目录中的目标
有时你可能不希望某些子目录中的目标在默认构建过程中被构建。你可以使用 EXCLUDE_FROM_ALL 选项来排除这些目标。
假设你有一个测试子目录 tests,并且你不希望测试目标在默认构建过程中被构建:

MyProject/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── tests/
    ├── CMakeLists.txt
    └── test_main.cpp
    
    
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加 src 子目录
add_subdirectory(src)

# 添加 tests 子目录,并排除其目标
add_subdirectory(tests EXCLUDE_FROM_ALL)


#tests/CMakeLists.txt 文件可以定义测试目标:
# tests/CMakeLists.txt
add_executable(MyTests test_main.cpp)

# 链接到 GoogleTest 库
find_package(GTest REQUIRED)
target_link_libraries(MyTests PRIVATE GTest::GTest GTest::Main)

多级子目录
add_subdirectory 支持多级嵌套的子目录结构。你可以在一个子目录中再次使用
add_subdirectory 来包含更深层次的子模块。

MyProject/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   ├── main.cpp
│   └── utils/
│       ├── CMakeLists.txt
│       └── utils.cpp
└── tests/
    ├── CMakeLists.txt
    └── test_main.cpp
    
 # 主项目内部的cmakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加 src 子目录
add_subdirectory(src)

# 添加 tests 子目录,并排除其目标
add_subdirectory(tests EXCLUDE_FROM_ALL) 
    

#src/CMakeLists.txt 文件可以包含 utils 子目录: 
# src/CMakeLists.txt
add_subdirectory(utils)

# 创建可执行文件
add_executable(MyExecutable main.cpp)

# 链接到 utils 库
target_link_libraries(MyExecutable PRIVATE UtilsLibrary)      

 ##src/utils/CMakeLists.txt 文件可以创建一个静态库: 
 # src/utils/CMakeLists.txt
add_library(UtilsLibrary STATIC utils.cpp)

变量传递信息
在父目录的 CMakeLists.txt 文件中定义变量,并将这些变量传递给子目录中的 CMake 脚本。这有助于在不同模块之间共享配置信息


eg
cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 定义项目版本
set(MY_PROJECT_VERSION "1.0.0")

# 添加 src 子目录
add_subdirectory(src)

##src/CMakeLists.txt 文件可以使用这个变量:cmake
# src/CMakeLists.txt
message(STATUS "Building project version: ${MY_PROJECT_VERSION}")

# 创建可执行文件
add_executable(MyExecutable main.cpp)

处理外部项目
add_subdirectory 还可以用于将外部项目纳入当前项目。你可以通过 ExternalProject_Add 模块下载和构建外部项目,然后使用 add_subdirectory 将其纳入主项目中。

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 下载并构建 GoogleTest
include(ExternalProject)
ExternalProject_Add(googletest
  PREFIX ${CMAKE_BINARY_DIR}/external/googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG release-1.10.0
  INSTALL_COMMAND ""
)

# 添加 GoogleTest 的源代码目录
add_subdirectory(${CMAKE_BINARY_DIR}/external/googletest/src/googletest/googletest _deps/googletest-build)

# 查找 GoogleTest 库
find_package(GTest REQUIRED)

2.3.12 project

project 命令用于设置工程名称
project(HELLO)
执行这个之后会引入两个变量:HELLO_SOURCE_DIR 和 HELLO_BINARY_DIR,注意这两个变量名的前缀就是工程名称,HELLO_SOURCE_DIR 变量指的是 HELLO 工程源码目录、HELLO_BINARY_DIR 变量指的是 HELLO 工程源码的输出文件目录;

2.4 Cmake的宏定义

| 变量 |  说明|
|--|--|
|  |  |

在这里插入图片描述

  • set(BUILD_SHARED_LIBS on)
    对于 add_library()命令,当没有显式指定生成动态库时(SHARED 选项),默认生成的是静态库。当设置BUILD_SHARED_LIBS 为on 时,表示生成的动态库,当变量BUILD_SHARED_LIBS 为OFF的时候 默认生成的静态库
  • CMAKE_BUILD_TYPE
#Release 版本   
set(CMAKE_BUILD_TYPE Release)
# Debug 版本
set(CMAKE_BUILD_TYPE Debug)
  • CMAKE_INCLUDE_PATH
    指定搜索路径的目录列表, find_file()和 find_path()
cmake_minimum_required("VERSION" "3.29")
project(HELLO VERSION 1.1.0) 
#设置 CMAKE_INCLUDE_PATH 变量
set(CMAKE_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/src)
# 查找文件
find_file(FILE_PATH cinfig.in)
  • CMAKE_LIBRARY_PATH
    指定 find_library()命令的搜索路径的目录列表
  • CMAKE_INCLUDE_DIRECTORIES_BEFORE
    变量可以改变 include_directories()命令的行为, 通常情况下include_directories()命令默认将目录添加到列表的后面,
    如果将 CMAKE_INCLUDE_DIRECTORIES_BEFORE 设置为 on, 则include_directories()命令会将目录添加到列表前面; 同理若将 CMAKE_INCLUDE_DIRECTORIES_BEFORE设置为 off 或未设置该变量, include_directories()会将目录添加到列表后面
  • CMAKE_IGNORE_PATH
    指定相关搜索函数忽略相关的目录,如find_program()、 find_library()、 find_file()和 find_path()命令忽略的目录列表

在这里插入图片描述

2.5 Cmake流程控制

2.5.1 if

if(expression)
     # then section.
    command1(args ...)
    command2(args ...)

elseif(expression2)  
     command1(args ...)
     command2(args ...)
else(expression)
    command1(args ...)
endif(expression)

NOT
AND
OR
EXISTS path path 指定的文件或目录存在
IS_DIRECTORY path path 指定的路径为目录
IS_SYMLINK path path 为符号链接
IS_ABSOLUTE path path 为绝对路径
<variable|string> IN_LIST 右边列表中包含左边的元素
<variable|string> LESS <variable|string> 如果给定的字符串或变量的值是有效数字且大于右侧的数字,则
为真。
<variable|string> GREATER <variable|string> 如果给定的字符串或变量的值是有效数字并且等于右侧的值,则
为真

2.5.2 foreach 循环 while break、continue

和普通的C 语言类似

3、CMake的高级使用

3.1 函数

function(<name> [arg1 [arg2 ...]])
  # 函数体
endfunction()
  • :函数的名称。
  • arg1 arg2 …:函数的参数(可选)。
  • 在 cmake 中,调用函数时实际传入的参数个数不需要等于函数定义的参数个数(甚至函数定义时,参数个数为 0) ,但是实际传入的参数个数必须大于或等于函数定义的参数个数
    function()定义的函数它的使用范围是全局的,并不局限于当前源码、可以在其子源码或者父源码中被使用

关键特性
1.作用域隔离
:函数内的变量是局部的,只在函数内部可见。即使你在函数内部定义了与外部同名的变量,也不会影响外部的变量。
2.返回值
:CMake 函数本身没有直接的返回值机制,但你可以通过修改传递给函数的变量来实现返回值的效果。通常使用 PARENT_SCOPE 或 CACHE 来将结果传递回调用者。
3.参数传递
:函数参数可以通过位置传递,类似于 C/C++ 中的参数传递方式。你还可以使用 ARGN、ARGV 和 ARGC 来处理可变数量的参数:

  • ARGN:表示除前几个命名参数之外的所有参数。
  • ARGV:表示所有传递给函数的参数列表。
  • ARGC:表示传递给函数的参数总数。
    4.递归调用
    :CMake 函数支持递归调用,即函数可以在其内部调用自己
function(create_library lib_name)
  add_library(${lib_name} STATIC ${lib_name}.cpp)
  target_include_directories(${lib_name} PRIVATE include)
  target_compile_options(${lib_name} PRIVATE -Wall -Wextra)
endfunction()

# 使用函数创建多个库
create_library(MyLib1)
create_library(MyLib2)
create_library(MyLib3)

处理可变参数
可以使用 ARGN、ARGV 和 ARGC 来实现。

function(print_args first_arg)
  message(STATUS "First argument: ${first_arg}")
  message(STATUS "Remaining arguments: ${ARGN}")
endfunction()

# 调用函数并传递多个参数
print_args(arg1 arg2 arg3 arg4)

传递引用参数
函数修改传递给它的变量。你可以通过引用传递参数,使用 PARENT_SCOPE 将修改后的值传递回父作用域。

function(increment var)
  math(EXPR new_value "${${var}} + 1")
  set(${var} ${new_value} PARENT_SCOPE)
endfunction()

# 定义一个变量
set(my_var 5)

# 调用函数并修改变量
increment(my_var)

# 输出修改后的变量
message(STATUS "my_var after increment: ${my_var}")  # 输出: my_var after increment: 6
  • 作用域
    函数作用域
    当在函数内通过 set 将变量 tmp ,变量 var
    仅在函数作用域内有效,出了函数变量无效。
    如果函数内没有定义变量 tmp, 函数会想函数外搜索tmp变量 直到找到相关变量。

函数内如何设置外部定义的变量

#加上 PARENT_SCOPE
function(xyz)
    set(ABC "qq!" PARENT_SCOPE) 
endfunction()
  • 目录作用域
    子目录会将父目录的所有变量拷贝到当前 CMakeLists.txt 源码中,当前 CMakeLists.txt 中的变量的作用域仅在当前目录有效。所有从父目录中传下来的值 向下有效,进行值拷贝,更改后无法返回父目录

3.2 macro

与function 不同,macro 没有独立的作用域,所有变量都是全局的。这意味着在 macro 内部定义或修改的变量会直接影响外部作用域。虽然这可能会导致一些潜在的问题(如变量冲突),但在某些情况下,macro 的这种特性可以带来便利。

macro(<name> [arg1 [arg2 ...]])
  # 宏体
endmacro()

<name>:宏的名称。
arg1 arg2 ...:宏的参数(可选)。

关键特性
1.全局作用域
:macro 内部的所有变量都是全局的,即使你在宏内部定义了一个新变量,它也会在宏外部可见。这使得 macro 在处理全局状态时非常方便,但也容易导致意外的行为。
2.参数传递
:宏参数可以通过位置传递,类似于 C/C++ 中的参数传递方式。你还可以使用 ARGN、ARGV 和 ARGC 来处理可变数量的参数:

  • ARGN:表示除前几个命名参数之外的所有参数。
  • ARGV:表示所有传递给宏的参数列表。
  • ARGC:表示传递给宏的参数总数。
    3.无返回值机制
    :macro 没有直接的返回值机制,但你可以通过修改传递给宏的变量来实现类似的效果。
    4.性能优势
    :由于 macro 是在调用时直接展开的,而不是像 function 那样创建新的作用域,因此 macro 在性能上可能略优于 function。然而,这种性能差异通常是可以忽略不计的,除非你在宏中执行非常频繁的操作。
    5.递归调用
    :macro 不支持递归调用。如果你尝试在宏内部调用自己,CMake 会报错。
macro(create_library lib_name)
  add_library(${lib_name} STATIC ${lib_name}.cpp)
  target_include_directories(${lib_name} PRIVATE include)
  target_compile_options(${lib_name} PRIVATE -Wall -Wextra)
endmacro()

# 使用宏创建多个库
create_library(MyLib1)
create_library(MyLib2)
create_library(MyLib3)

3.3 文件操作

cmake 提供了 file()命令可对文件进行一系列操作,譬如读写文件、删除文件、文件重命名、拷贝文件、
创建目录等等
file 命令的一些常见用法:

  • 复制和安装文件:使用 COPY 和 INSTALL 操作来复制和安装文件或目录。
  • 删除文件和目录:使用 REMOVE 和 REMOVE_RECURSE 操作来删除文件和目录。
  • 重命名文件和目录:使用 RENAME 操作来重命名文件和目录。
  • 读取和写入文件:使用 READ、WRITE 和 APPEND 操作来读取和写入文件内容。
  • 查找文件:使用 GLOB 和 GLOB_RECURSE 操作来查找匹配特定模式的文件。
  • 创建目录:使用 MAKE_DIRECTORY 操作来创建目录。
  • 计算校验和:使用 SHA1、MD5、SHA256 等操作来计算文件的校验和。
  • 获取文件信息:使用 TIMESTAMP 和 SIZE 操作来获取文件的时间戳和大小。
  • 更改文件权限:使用 CHMOD 操作来更改文件或目录的权限。
  • 路径转换:使用 TO_CMAKE_PATH 和 TO_NATIVE_PATH 操作来转换路径格式。
    COPY 和 INSTALL
  • COPY:用于将文件或目录从一个位置复制到另一个位置。支持递归复制目录。
  • INSTALL:用于定义安装规则,指定在安装时将文件或目录复制到目标位置。
# 复制单个文件
file(COPY input.txt DESTINATION ${CMAKE_BINARY_DIR}/output)

# 复制整个目录及其内容
file(COPY src/ DESTINATION ${CMAKE_BINARY_DIR}/include FILES_MATCHING PATTERN "*.h")

# 定义安装规则
install(FILES input.txt DESTINATION bin)
install(DIRECTORY src/ DESTINATION include FILES_MATCHING PATTERN "*.h")

REMOVE 和 REMOVE_RECURSE

  • REMOVE:用于删除单个文件。如果文件不存在,不会报错。
  • REMOVE_RECURSE:用于递归删除文件和目录。如果目录为空或不存在,不会报错
# 删除单个文件
file(REMOVE output.txt)

# 递归删除目录及其内容
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/build)

RENAME

# 重命名文件
file(RENAME old_name.txt new_name.txt)

# 重命名目录
file(RENAME old_dir new_dir)

READ 和 WRITE

  • READ:用于读取文件内容并将其存储在一个变量中。
  • WRITE:用于将字符串写入文件。如果文件已存在,则会覆盖原有内容。
  • APPEND:用于将字符串追加到文件末尾。如果文件不存在,则会创建新文件。
# 读取文件内容
file(READ input.txt content)
message(STATUS "File content: ${content}")

# 写入文件
file(WRITE output.txt "Hello, World!")

# 追加内容到文件
file(APPEND output.txt "\nThis is an appended line.")

GLOB 和 GLOB_RECURSE

  • GLOB:用于查找匹配特定模式的文件,并将结果存储在一个变量中。只查找当前目录中的文件。
  • GLOB_RECURSE:类似于 GLOB,但会递归查找子目录中的文件
# 查找当前目录中的所有头文件
file(GLOB header_files *.h)

# 递归查找所有头文件
file(GLOB_RECURSE all_header_files *.h)

# 打印找到的文件列表
foreach(header_file IN LISTS header_files)
  message(STATUS "Header file: ${header_file}")
endforeach()

MAKE_DIRECTORY
MAKE_DIRECTORY:用于创建一个或多个目录。如果目录已经存在,不会报错

# 计算相对路径
file(RELATIVE_PATH relative_path ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
message(STATUS "Relative path: ${relative_path}")
TIMESTAMP
TIMESTAMP:用于获取文件的最后修改时间戳
# 获取文件的时间戳
file(TIMESTAMP input.txt last_modified_time "%Y-%m-%d %H:%M:%S")
message(STATUS "Last modified time: ${last_modified_time}")

SHA1, MD5, SHA256, SHA384, SHA512

  • SHA1:用于计算文件的 SHA-1 校验和。
  • MD5:用于计算文件的 MD5 校验和。
  • SHA256:用于计算文件的 SHA-256 校验和。
  • SHA384:用于计算文件的 SHA-384 校验和。
  • SHA512:用于计算文件的 SHA-512 校验和。
# 计算文件的 SHA-256 校验和
file(SHA256 input.txt checksum)
message(STATUS "SHA-256 checksum: ${checksum}")

CHMOD

  • CHMOD:用于更改文件或目录的权限。支持 Unix 风格的权限符号(如 +x 表示添加可执行权限)。
# 添加可执行权限
file(CHMOD script.sh +x)

# 更改文件权限为 644
file(CHMOD script.sh 644)

TO_CMAKE_PATH 和 TO_NATIVE_PATH

  • TO_CMAKE_PATH:将路径转换为 CMake 风格的路径(使用 / 作为路径分隔符)。
  • TO_NATIVE_PATH:将路径转换为平台原生风格的路径(Windows 使用 \,Unix 使用 /)。
# 将路径转换为 CMake 风格
file(TO_CMAKE_PATH "C:\\path\\to\\file" cmake_path)
message(STATUS "CMake path: ${cmake_path}")

# 将路径转换为平台原生风格
file(TO_NATIVE_PATH "/path/to/file" native_path)
message(STATUS "Native path: ${native_path}")

3.4 属性

在 CMake 中,属性(Properties)用于存储和管理与项目、目标、源文件、目录等相关的元数据。属性可以用来控制构建行为、传递配置信息、设置编译选项等。CMake 提供了丰富的属性类型,并且可以通过 set_property 和 get_property 命令来设置和获取这些属性。
属性的作用范围
CMake 中的属性可以根据其作用范围分为以下几类:
1.全局属性(Global Properties):适用于整个 CMake 项目。
2.目录属性(Directory Properties):适用于当前目录及其子目录。
3.目标属性(Target Properties):适用于特定的目标(如可执行文件、库等)。
4.源文件属性(Source File Properties):适用于特定的源文件。
5.测试属性(Test Properties):适用于 CTest 管理的测试用例。
6.缓存变量(Cache Variables):类似于属性,但主要用于用户配置和命令行传递的选项

set_property(<scope> <object> PROPERTY <property_name> [value1 [value2 ...]])

<scope>:指定属性的作用范围,可以是 GLOBAL、DIRECTORY、TARGET、SOURCE 或 TEST。
<object>:指定要设置属性的对象,具体取决于作用范围。例如,对于 TARGET,<object> 是目标名称;对于 SOURCE,<object> 是源文件路径。
PROPERTY:关键字,表示接下来是要设置属性。
<property_name>:属性的名称。
[value1 [value2 ...]]:属性的值,可以是一个或多个。

get_property(<variable> <scope> <object> PROPERTY <property_name> [SET | DEFINED])

<variable>:用于存储获取到的属性值的变量名。
<scope>:指定属性的作用范围,可以是 GLOBAL、DIRECTORY、TARGET、SOURCE 或 TEST。
<object>:指定要获取属性的对象,具体取决于作用范围。
PROPERTY:关键字,表示接下来是要获取属性。
<property_name>:属性的名称。
[SET | DEFINED]:可选参数,用于检查属性是否已设置或定义。

常见属性

  1. 全局属性
    全局属性适用于整个 CMake 项目。常见的全局属性包括:
  • WARN_UNINITIALIZED_VARIABLES:启用对未初始化变量的警告。
  • WARN_AMBIGUOUS_VARIABLES:启用对模糊变量的警告。
# 启用未初始化变量警告
set_property(GLOBAL PROPERTY WARN_UNINITIALIZED_VARIABLES TRUE)
  1. 目录属性
    目录属性适用于当前目录及其子目录。常见的目录属性包括:
  • INCLUDE_DIRECTORIES:指定头文件的搜索路径。
  • COMPILE_DEFINITIONS:定义预处理器宏。
  • CMAKE_CXX_STANDARD:指定 C++ 标准版本。
# 设置当前目录的头文件搜索路径
set_property(DIRECTORY PROPERTY INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/include)

# 定义预处理器宏
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS DEBUG=1)

3.目标属性
目标属性适用于特定的目标(如可执行文件、库等)。常见的目标属性包括:

  • POSITION_INDEPENDENT_CODE:启用位置无关代码(PIC),通常用于动态库。
  • CXX_STANDARD:指定 C++ 标准版本。
  • RUNTIME_OUTPUT_DIRECTORY:指定可执行文件的输出目录。
  • ARCHIVE_OUTPUT_DIRECTORY:指定静态库的输出目录。
  • LIBRARY_OUTPUT_DIRECTORY:指定动态库的输出目录。
  • LINK_LIBRARIES:指定链接的库。
  • COMPILE_FLAGS:指定编译器选项。
  • COMPILE_DEFINITIONS:定义预处理器宏。
  • INTERFACE_INCLUDE_DIRECTORIES:指定接口头文件的搜索路径。
  • INTERFACE_LINK_LIBRARIES:指定接口链接的库
# 创建一个可执行文件
add_executable(MyExecutable main.cpp)

# 启用位置无关代码
set_property(TARGET MyExecutable PROPERTY POSITION_INDEPENDENT_CODE TRUE)

# 指定 C++ 标准版本
set_property(TARGET MyExecutable PROPERTY CXX_STANDARD 17)

# 设置输出目录
set_property(TARGET MyExecutable PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# 链接到第三方库
set_property(TARGET MyExecutable PROPERTY LINK_LIBRARIES ThirdPartyLib)

# 定义预处理器宏
set_property(TARGET MyExecutable PROPERTY COMPILE_DEFINITIONS DEBUG=1)

# 指定接口头文件的搜索路径
set_property(TARGET MyExecutable PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/include)

# 指定接口链接的库
set_property(TARGET MyExecutable PROPERTY INTERFACE_LINK_LIBRARIES ThirdPartyLib)

4.源文件属性
源文件属性适用于特定的源文件。常见的源文件属性包括:

  • COMPILE_FLAGS:指定编译器选项。
  • OBJECT_DEPENDS:指定依赖的文件。
  • GENERATED:标记文件为生成文件。
  • SKIP_AUTOMOC:跳过自动 moc 处理(适用于 Qt 项目)
# 设置特定源文件的编译器选项
set_property(SOURCE main.cpp PROPERTY COMPILE_FLAGS -Wall -Wextra)

# 标记文件为生成文件
set_property(SOURCE generated.cpp PROPERTY GENERATED TRUE)

# 跳过自动 moc 处理
set_property(SOURCE main.cpp PROPERTY SKIP_AUTOMOC TRUE)
  1. 测试属性
    测试属性适用于 CTest 管理的测试用例。常见的测试属性包括:
  • TIMEOUT:设置测试的最大运行时间。
  • ENVIRONMENT:设置测试的环境变量。
  • LABELS:为测试添加标签。
  • WORKING_DIRECTORY:设置测试的工作目录
# 添加一个测试
add_test(NAME MyTest COMMAND MyExecutable)

# 设置测试的最大运行时间为 60set_property(TEST MyTest PROPERTY TIMEOUT 60)

# 设置测试的环境变量
set_property(TEST MyTest PROPERTY ENVIRONMENT "VAR1=value1" "VAR2=value2")

# 为测试添加标签
set_property(TEST MyTest PROPERTY LABELS "unit" "fast")

# 设置测试的工作目录
set_property(TEST MyTest PROPERTY WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test)

缓存变量
缓存变量类似于属性,但主要用于用户配置和命令行传递的选项。常见的缓存变量包括:

  • CMAKE_BUILD_TYPE:指定构建类型(如 Debug、Release)。
  • CMAKE_INSTALL_PREFIX:指定安装路径。
  • CMAKE_CXX_COMPILER:指定 C++ 编译器。
# 设置构建类型
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the build type: Debug or Release")

# 设置安装路径
set(CMAKE_INSTALL_PREFIX /usr/local CACHE PATH "Installation directory")

# 设置 C++ 编译器
set(CMAKE_CXX_COMPILER clang++ CACHE FILEPATH "C++ compiler")

3.5 安装
install 命令用于定义安装规则,指定在执行 、make install 或 cmake --install . 时将哪些文件或目标复制到指定的目标位置。通过 install 命令,你可以控制项目的安装过程,确保生成的可执行文件、库文件、头文件等被正确地放置到系统中的适当位置。
install( […])

常见选项

  • DESTINATION
    :指定安装的目标路径。通常是相对于 CMAKE_INSTALL_PREFIX 的相对路径。默认情况下,CMAKE_INSTALL_PREFIX 是 /usr/local,但可以根据需要更改。
  • FILES_MATCHING
    :用于过滤要安装的文件。你可以使用 PATTERN 指定文件名模式(如 *.h 表示所有头文件),或者使用 EXCLUDE 排除某些文件。
  • PERMISSIONS
    :用于设置安装文件的权限。你可以为所有者、组和其他用户分别设置读、写、执行权限。
  • COMPONENT
    :用于定义安装组件。用户可以选择安装特定的组件,而不是安装所有内容。
  • CONFIGURATIONS
    :用于指定安装规则只在某些构建配置(如 Debug 或 Release)下生效。
  • OPTIONAL
    :如果指定的文件或目录不存在,不会报错。这对于处理可选文件非常有用。
  1. 安装目标(可执行文件、库文件)
    install 命令最常见的用法是安装由 add_executable 或 add_library 创建的目标。你可以使用 TARGETS 子命令来指定要安装的目标,并设置安装路径
# 定义一个可执行文件
add_executable(MyExecutable main.cpp)

# 定义一个静态库
add_library(MyStaticLib STATIC utils.cpp)

# 定义一个动态库
add_library(MySharedLib SHARED network.cpp)

# 安装可执行文件到 bin 目录
install(TARGETS MyExecutable DESTINATION bin)

# 安装静态库到 lib 目录
install(TARGETS MyStaticLib DESTINATION lib)

# 安装动态库到 lib 目录
install(TARGETS MySharedLib DESTINATION lib)
  1. 安装单个文件
    使用 FILES 子命令来安装单个文件。这通常用于安装配置文件、资源文件等
# 安装配置文件到 etc 目录
install(FILES config.txt DESTINATION etc)

# 安装多个文件
install(FILES file1.txt file2.txt file3.txt DESTINATION share/myproject)
  1. 安装整个目录
    用 DIRECTORY 子命令来安装整个目录及其内容
# 安装 include 目录及其所有头文件到系统的 include 目录
install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN "*.h")

# 安装 resources 目录及其所有内容到 share/myproject 目录
install(DIRECTORY resources/ DESTINATION share/myproject)

4.安装权限设置

# 安装可执行文件并设置权限为 755
install(TARGETS MyExecutable DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)

# 安装配置文件并设置权限为 644
install(FILES config.txt DESTINATION etc PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)

5.安装后执行自定义命令
使用 CODE 子命令来在安装过程中执行任意的 CMake 代码。这在需要执行一些额外的安装步骤时非常有用,例如生成配置文件、运行脚本等

# 安装后生成配置文件
install(CODE "file(WRITE \${CMAKE_INSTALL_PREFIX}/etc/myproject.conf \"# Generated configuration file\")")

# 安装后运行自定义脚本
install(CODE "execute_process(COMMAND \${CMAKE_SOURCE_DIR}/scripts/post_install.sh)")

6.安装条件
可以使用 COMPONENT 选项来定义安装组件,并根据用户的选择安装不同的部分

# 定义安装组件
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME runtime)

# 安装可执行文件到 runtime 组件
install(TARGETS MyExecutable DESTINATION bin COMPONENT runtime)

# 安装库文件到 development 组件
install(TARGETS MySharedLib DESTINATION lib COMPONENT development)
# 安装头文件到 development 组件
install(DIRECTORY include/ DESTINATION include COMPONENT development FILES_MATCHING PATTERN "*.h")

eg:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加可执行文件
add_executable(MyExecutable main.cpp)

# 添加共享库
add_library(MySharedLib SHARED utils.cpp)

# 安装可执行文件到 runtime 组件
install(TARGETS MyExecutable DESTINATION bin COMPONENT runtime)

# 安装共享库和头文件到 development 组件
install(TARGETS MySharedLib DESTINATION lib COMPONENT development)
install(DIRECTORY include/ DESTINATION include COMPONENT development FILES_MATCHING PATTERN "*.h")



cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 添加可执行文件
add_executable(MyExecutable main.cpp)

# 安装可执行文件
install(TARGETS MyExecutable DESTINATION bin)

# 安装后生成配置文件
install(CODE "
  file(WRITE \${CMAKE_INSTALL_PREFIX}/etc/myproject.conf
    \"# Configuration file for MyProject\n\"
    \"Version: ${PROJECT_VERSION}\n\"
    \"Installed on: \${CMAKE_CURRENT_DATE_TIME}\n\"
  )
")

3.6 CACHE

CACHE 是一个用于存储持久性变量的机制。这些变量可以在配置过程中被保存,并在后续的构建或配置过程中重新加载。CACHE 变量通常用于存储用户输入、路径、选项等信息,以便在不同的构建会话之间保持一致。通过 CACHE,你可以让 CMake 项目更加灵活和可配置。
CACHE 变量的作用
1.持久化存储
:CACHE 变量在配置过程中被保存到 CMakeCache.txt 文件中,这样即使你重新运行 cmake,这些变量的值也会被保留。
2.用户输入
:CACHE 变量可以用于存储用户通过命令行或交互式配置工具(如 ccmake 或 cmake-gui)提供的输入。
3.默认值
:你可以为 CACHE 变量设置默认值,用户可以在配置过程中修改这些值。
4.类型和描述
:每个 CACHE 变量都有一个类型(如 STRING、PATH、FILEPATH 等),并且可以附带一个描述字符串,帮助用户理解该变量的用途。

set(<variable> <value> CACHE <type> <docstring> [FORCE])
  • :要设置的变量名。
  • :变量的初始值。
  • :变量的类型,常见的类型包括:
  • BOOL:布尔值(ON 或 OFF)。
  • STRING:字符串。
  • PATH:文件夹路径。
  • FILEPATH:文件路径。
  • INTERNAL:内部变量,不会显示给用户。
  • :变量的描述字符串,帮助用户理解该变量的用途
  • [FORCE]:可选参数,如果指定,则强制覆盖已有的 CACHE 变量,即使它已经被用户修改过。

设置 CACHE 变量

# 设置一个布尔类型的缓存变量,默认值为 ON
set(ENABLE_TESTS ON CACHE BOOL "Enable unit tests")

# 设置一个字符串类型的缓存变量,默认值为 "Debug"
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel.")

# 设置一个路径类型的缓存变量,默认值为当前源代码目录
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "Install path prefix")

# 设置一个文件路径类型的缓存变量,默认值为当前源代码目录下的 config.txt
set(CONFIG_FILE ${CMAKE_SOURCE_DIR}/config.txt CACHE FILEPATH "Path to the configuration file")



强制覆盖已有的 CACHE 变量,即使它已经被用户修改过,可以使用 FORCE 选项

# 强制设置 CMAKE_INSTALL_PREFIX 为 /usr/local
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Install path prefix" FORCE)

内部 CACHE 变量
希望将一些变量存储在 CACHE 中,但不希望它们显示给用户。你可以使用 INTERNAL 类型来实现这一点。

# 设置一个内部缓存变量,不会显示给用户
set(MY_INTERNAL_VAR "Some value" CACHE INTERNAL "This is an internal variable")

3.7 嵌入式下的交叉编译设置

需要设置对应的sysroot 以及交叉编译链和 编译参数 参考正点原子I.MX6U 的相关文档和环境进行展示
cmake_minimum_required(VERSION 3.29)

# 配置 ARM 交叉编译
set(CMAKE_SYSTEM_NAME Linux) 
#设置目标系统名字
set(CMAKE_SYSTEM_PROCESSOR arm)
#设置目标处理器架构# 指定编译器的 sysroot 路径
set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)

# 指定交叉编译器 arm-gcc 和 arm-g++ 一定要设置对,
# 不然编译的可执行文件不一定能运行
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/armpoky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linuxgnueabi/arm-poky-linux-gnueabi-g++)



# 为编译器添加编译选项
# 这些也要根据嵌入式板卡不一样 进行匹配设置
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")



set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Project(XXX)
add_executable(main main.c)
  • CMAKE_SYSROOT 变量的值会传递给 gcc 编译器的–sysroot 选项,也就是–sysroot=${CMAKE_SYSROOT}, --sysroot 选项指定了编译器的 sysroot 目录,也就是编译器的系统根目录, 编译过程中需要链接的库、 头文件等, 就会去该目录下寻找,譬如标准 C 库、标准 C 头文件这些。

  • CMAKE_C_COMPILER 变量指定了 C 语言编译器 gcc,由于是交叉编译,所以应该指定为 arm-gcc。

  • CMAKE_CXX_COMPILER 变量指定了 C++语言编译器 g++,由于是交叉编译,所以应该指定为 armg++。

  • CMAKE_C_FLAGS 变量为 gcc 编译器添加编译选项,

  • CMAKE_CXX_FLAGS 变量为 g++编译器添加编译选项

  • CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 和 CMAKE_FIND_ROOT_PATH_MODE_INCLUDE被设置为 ONLY; CMAKE_FIND_ROOT_PATH_MODE_INCLUDE 变量控制 CMAKE_SYSROOT 中的路径是否被 find_file()和 find_path()使用。如果设置为 ONLY,则只会搜索 CMAKE_SYSROOT 中的路径, 如果设置为 NEVER,则 CMAKE_SYSROOT 中的路径将被忽略并且仅使用主机系统路径。如果设置为 BOTH,则将搜索主机系统路径和 CMAKE_SYSROOT 中的路径。

  • CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 变量控制 CMAKE_SYSROOT 中的路径是否被find_library()使用, 如果设置为 ONLY,则只会搜索 CMAKE_SYSROOT 中的路径, 如果设置为 NEVER,则 CMAKE_SYSROOT 中的路径将被忽略并且仅使用主机系统路径。如果设置为 BOTH,则将搜索主机系统路径和 CMAKE_SYSROOT 中的路径

上面的CMake中详细配置了交叉编译的信息, 通常是江浙谢交叉编译信息放置在配置文件中,在编译到时候指定配置文件
如 cmake -DCMAKE_TOOLCHAIN_FILE=cfg_file_path
通过-DCMAKE_TOOLCHAIN_FILE 选项指定配置文件, -D 是 cmake 命令提供的一个选项,通过该选项可以创建一个缓存变量(缓存变量就是全局变量,在整个工程中都是生效的,会覆盖 CMakeLists.txt 源码中 定 义 的 同 名 变 量 ) , 所 以 -DCMAKE_TOOLCHAIN_FILE 其 实 就 是 设 置 了 缓 存 变 量CMAKE_TOOLCHAIN_FILE,它的值就是“=”号后面的内容, cmake 会执行 CMAKE_TOOLCHAIN_FILE变量所指定的源文件,对交叉编译进行设置
完整的例子如:

# armlinux-setup.cmake


set(CMAKE_SYSTEM_NAME Linux)
 #设置目标系统名字
 set(CMAKE_SYSTEM_PROCESSOR arm) 
 #设置目标处理器架构# 指定编译器的 sysroot 路径s
 et(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
 set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)

# 指定交叉编译器 arm-linux-gcc 和 arm-linux-g++
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/armpoky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linuxgnueabi/arm-poky-linux-gnueabi-g++)


# 为编译器添加编译选项
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")



set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

在进行编译的时候使用 xxx/cmake -DCMAKE_TOOLCHAIN_FILE= armlinux-setup.cmake

3.8 Cmake其他使用

3.8.1 add_custom_command()

CMake中的add_custom_command()是用于在构建过程中插入自定义命令的关键函数,主要解决生成文件、执行预处理/后处理操作等需求。

  • 生成文件模式(OUTPUT版本)
    用途​:当需要根据依赖文件动态生成源代码或其他构建资源时使用。
add_custom_command(
    OUTPUT output1 [output2 ...]
    COMMAND command1 [args...]
    [DEPENDS depend1 depend2 ...]
    [BYPRODUCTS [files...]]
    [WORKING_DIRECTORY dir]
    [COMMENT comment]
    [VERBATIM]
    [APPEND]
)

# COMMAND: 指定要运行的命令及其参数。
# DEPENDS: 列出命令所依赖的文件或其他目标。如果这些依赖项发生变化,命令将重新执行。
# BYPRODUCTS: 指定命令可能生成的额外文件(非输出文件)。
# WORKING_DIRECTORY: 指定命令的工作目录。
# COMMENT: 在命令执行时显示的注释信息。
# VERBATIM: 确保所有参数都被正确转义,防止 shell 解释特殊字符。
# APPEND: 将命令追加到现有的自定义命令中,而不是替换它。

eg 生成配置文件

# 定义输出文件路径
set(CONFIG_H ${CMAKE_BINARY_DIR}/config.h)

# 添加自定义命令生成 config.h
add_custom_command(
  OUTPUT ${CONFIG_H}
  COMMAND python ${PROJECT_SOURCE_DIR}/generate_config.py --output ${CONFIG_H}
  DEPENDS ${PROJECT_SOURCE_DIR}/generate_config.py
  COMMENT "生成 config.h 配置文件"
)

# 添加一个自定义目标,确保命令被触发
add_custom_target(GenerateConfig ALL DEPENDS ${CONFIG_H})

# 在构建主目标时依赖生成的文件
add_executable(my_app main.cpp ${CONFIG_H})

generate_config.py

import sys

with open(sys.argv[2], 'w') as f:
    f.write("#define CONFIG_VALUE 42\n")
  • 构建事件模式(TARGET版本)
    ​用途​:在特定目标(如可执行文件/库)的构建阶段前后执行命令。
add_custom_command(
  TARGET target_name          # 绑定的目标(通过add_executable/add_library创建)
  PRE_BUILD | PRE_LINK | POST_BUILD  # 执行时机
  COMMAND command1 [args...]  # 执行的命令
  [COMMENT "message"]         # 构建时提示信息
  [VERBATIM]
  [WORKING_DIRECTORY dir]
)

#执行时机​:
#​ PRE_BUILD​:目标编译前执行(仅部分生成器支持,如Visual Studio)
#  ​PRE_LINK​:编译完成后、链接前执行
#   ​POST_BUILD​:目标构建完成后执行

eg

# 构建后拷贝文件

add_executable(my_app main.cpp)
add_custom_command(
  TARGET my_app POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy 
    $<TARGET_FILE:my_app> 
    ${CMAKE_BINARY_DIR}/dist/
  COMMENT "Copying binary to dist folder"
)

# 生成MD5校验信息

add_custom_command(
  TARGET my_app POST_BUILD
  COMMAND md5sum $<TARGET_FILE:my_app> > md5.txt
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

3.9 Cmake在 Qt中的使用

使用qtCreator是尽量使用4.11以上 不然会出一些莫名的问题,不知道是不是本机装的有问题,使用qt6的版本可以编译

# 设置CMake最低版本要求
cmake_minimum_required(VERSION 3.16)

# 定义项目名称、版本和使用的编程语言(C++)
project(untitled1 VERSION 0.1 LANGUAGES CXX)

# 启用Qt的自动处理功能
set(CMAKE_AUTOUIC ON)  # 自动处理UI文件(.ui)
set(CMAKE_AUTOMOC ON)  # 自动处理Qt元对象系统(MOC)
set(CMAKE_AUTORCC ON)  # 自动处理资源文件(.qrc)

# 设置C++标准为C++17,且必须支持
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找Qt包,支持Qt5和Qt6,必须包含Widgets模块
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

# 定义项目源文件列表
set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mainwindow.h
        mainwindow.ui
)

# 根据Qt主版本号选择不同的构建方式
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    # Qt6使用qt_add_executable添加可执行文件
    qt_add_executable(untitled1
        MANUAL_FINALIZATION  # 手动完成最终化
        ${PROJECT_SOURCES}
    )
    # 定义Android目标属性的示例(注释掉)
    #    set_property(TARGET untitled1 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
    #                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
    # 更多信息参考: https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    # Qt5的处理方式
    if(ANDROID)
        # Android平台构建共享库
        add_library(untitled1 SHARED
            ${PROJECT_SOURCES}
        )
        # 定义Android包源目录的示例(注释掉)
        #    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        # 其他平台构建可执行文件
        add_executable(untitled1
            ${PROJECT_SOURCES}
        )
    endif()
endif()

# 链接Qt Widgets模块到目标
target_link_libraries(untitled1 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# 设置macOS/iOS的bundle标识符
# 从Qt 6.1开始自动设置MACOSX_BUNDLE_GUI_IDENTIFIER
# 但建议手动设置一个固定的bundle标识符
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.untitled1)
endif()
# 设置目标属性
set_target_properties(untitled1 PROPERTIES
    ${BUNDLE_ID_OPTION}  # bundle标识符
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}  # 完整版本号
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}  # 短版本号
    MACOSX_BUNDLE TRUE  # 这是一个macOS bundle
    WIN32_EXECUTABLE TRUE  # 这是一个Windows可执行文件
)

# 包含GNU安装目录定义
include(GNUInstallDirs)
# 安装目标
install(TARGETS untitled1
    BUNDLE DESTINATION .  # bundle安装目录
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}  # 库文件安装目录
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}  # 可执行文件安装目录
)

# 如果是Qt6,完成可执行文件的最终化
if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(untitled1)
endif()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值