五、scope 作用域

5.1 作用域的类型

5.1.1 全局作用域

  • 从CMake脚本开始运行到结束。
  • 所有CMakeLists.txt文件共享全局作用域
  • 通过set()定义的变量默认是全局变量(除非明确在函数或宏中设置为局部变量)
set(GLOBAL_VAR "I am global")
message(${GLOBAL_VAR}) # 输出: I am global

5.1.2 目录作用域

  • 每个目录中的CMakeLists.txt文件有独立的作用域
  • 子目录可以继承父目录的变量,但子目录中对变量的更改不会影响父目录,只在子目录下生效
    下面是一个demo,理解即可,暂时不尝试自己编译:
# 顶层 CMakeLists.txt
set(DIR_VAR "Parent Directory")
# 添加子目录
add_subdirectory(subdir)
# 子目录 subdir/CMakeLists.txt
message(${DIR_VAR}) # 输出: Parent Directory
set(DIR_VAR "Child Directory")

子目录进行set操作后,顶层的 DIR_VAR 不会被子目录的更改影响。

5.1.3 函数作用域

  • 在函数中定义的变量是局部变量,只在函数内有效
  • 使用set()定义变量时,作用域局限于当前函数,除非指定PARENT_SCOPE
function(set_local_var)
  set(LOCAL_VAR "Local Scope")
  message(${LOCAL_VAR}) # 输出: Local Scope
endfunction()

set_local_var()
message(${LOCAL_VAR}) # 输出: (空,因为超出函数作用域)

六、宏

6.1 基本语法

macro(<name> [arg1 [arg2 ...]])
  # 宏体
endmacro()
  • <name> 是宏的名称。
  • [arg1, arg2…] 是参数列表(可选)。
  • 宏没有返回值,但可以通过变量或 CACHE 修改全局状态。

6.2 演示代码

cmake_minimum_required(VERSION 3.20.0)

macro(Test myVar)
    set(myVar "new value")
    message ("2. argument: ${myVar}")
endmacro()

set(myVar "First value")
message ("1. myVar: ${myVar}")
Test("value")
message("3. myVar: ${myVar}")

CMake简单使用(二)-LMLPHP
CMake简单使用(二)-LMLPHP

七、CMake构建项目

7.1 全局变量

在[[#1.2 初体验]]中,我们使用了以下代码:

project(hello CXX)
  • 声明项目名称为 hello,并设置主要语言为 C++(CXX)。
  • 生成项目相关的全局变量,例如:
    • ${PROJECT_NAME}:值为 hello
    • ${PROJECT_SOURCE_DIR}:项目的根目录路径。
    • ${PROJECT_BINARY_DIR}:构建目录路径。

7.2 写入源码路径

特点

  • 最简单的构建方式。
  • 适用于只有一个 CMakeLists.txt 文件的小型项目。
    目录结构
MyProject/
├── CMakeLists.txt
├── main.cpp
├──Transportation
	├──train.h
	├──tarin.cpp

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)

project(MyProject CXX)
add_executable(MyProject main.cpp 需要链接的文件路径)
  • CXX 表示语言是 cpp
  • 在原文件中,要写对应的.h文件,main.cpp:#include "Transportation/train.h",在这里就不需要再包含,但是要包含.cpp文件的路径
    构建方法
cmake -B build
cmake --build build
build/项目名称

CMake简单使用(二)-LMLPHP

7.3 调用子目录cmake脚本

  • include方法可以引入子目录中的cmake后缀的配置文件
  • 将配置加入 add_executable
    目录结构:
.
├── CMakeLists.txt
├── main.cpp
└── Transportation
    ├── car.cpp
    ├── car.h
    ├── train.cpp
    ├── train.h
    └── transportation.cmake

transportation.cmake使用set将变量设置成需要链接的文件:

set(trans_sources Transportation/car.cpp Transportation/train.cpp)

然后在CMakeLists.txt中就可以直接使用该变量:

cmake_minimum_required(VERSION 3.20.0)
project(MyProject CXX)
include(Transportation/transportation.cmake)
add_executable(MyProject main.cpp ${trans_sources})

❗️注意:要使用include引入子目录下的cmake文件,使用变量要用${}
构建方法同上。

7.4 CMakeLists 嵌套(最常用)

  • target_include_directories 头文件目录的声明
  • target_link_libraries 连接库文件
  • add_subdirectory 添加子目录
  • add_ library 生成库文件(默认静态库)
    目录结构:
.
├── CMakeLists.txt
├── main.cpp
└── Transportation
    ├── car.cpp
    ├── car.h
    ├── CMakeLists.txt
    ├── train.cpp
    └── train.h

在子目录的CMakeLists.txt中,add_library 命令创建一个名为 Translib 的库,该库包含 train.cpp 和 car.cpp 两个源文件的编译结果。

add_library(Translib train.cpp car.cpp)

在父目录的CMakeLists.txt中,

cmake_minimum_required(VERSION 3.20.0)
project(MyProject CXX)

add_subdirectory(Transportation)

add_executable(MyApp main.cpp)

target_link_libraries(MyApp PUBLIC Translib)

target_include_directories(MyApp PUBLIC "${PROJECT_SOURCE_DIR}/Transportation")
  1. 定义项目
  • project(MyProject CXX) 声明项目的名称为 MyProject,并设置其主要语言为 C++。
  1. 添加子目录
  • add_subdirectory(Transportation) 将 Transportation 子目录的构建逻辑纳入父目录中。
  • 这会递归执行 Transportation 目录中的 CMakeLists.txt,构建 Translib 库。
  1. 添加可执行文件
  • add_executable(MyApp main.cpp) 声明一个名为 MyApp 的可执行目标,其源文件为 main.cpp。
  1. 链接库
  • target_link_libraries(MyApp PUBLIC Translib) 将 Translib 库链接到可执行文件 MyApp。
  • PUBLIC:表示 Translib 的头文件路径和链接库信息都对 MyProject 和其使用者可见。
  1. 包含头文件路径
  • target_include_directories(MyApp PUBLIC "${PROJECT_SOURCE_DIR}/Transportation") 明确告诉 CMake,MyProject 需要包含 Transportation 目录中的头文件(如 car.h 和 train.h),这样在main.cpp中包含头文件时就不需要再次包含路径。
    构建顺序总结
  1. 进入父目录的构建流程
  • cmake 检测到 add_subdirectory(Transportation),递归构建子目录。
  • 子目录生成 Translib 库。
  1. 构建父目录目标
  • 构建 main.cpp 并将其链接到已生成的 Translib。

八、CMake 与库

8.1 CMake生成动静态库

8.1.1 动静态库

静态库
常见命名:.a(Linux/Unix),.lib(Windows)。

动态库
常见命名:.so(Linux/Unix),.dll(Windows),.dylib(macOS

8.1.2 常见命令

  • file(GLOB ...) 常用于搜索源文件
  • add_library(animal STATIC ${SRC}) 生成静态库
  • add_library(animal SHARED ${SRC}) 生成动态库
  • ${LIBRARY_OUTER_PATH} 导出目录

8.1.3 生成静态库

8.1.3.1 示例

当前目录结构如下:

.
├── CMakeLists.txt
├── include
│   ├── car.h
│   └── train.h
└── src
    ├── car.cpp
    └── train.cpp

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20.0)
project(MyProject CXX)

file(GLOB SRC &${PROJECT_SOURCE_DIR}/src/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include) 

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)
add_library(trans STATIC ${SRC})

下面来解释一下上面的代码:

project(MyProject CXX)

在[[#7.1 全局变量]]中,我们了解到使用project时,除声明项目名称外,还会生成项目相关的全局变量,例如:

  • ${PROJECT_NAME}:值为 MyProject。
  • ${PROJECT_SOURCE_DIR}:项目的根目录路径。
  • ${PROJECT_BINARY_DIR}:构建目录路径。

file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)

作用

  • 使用 file(GLOB ...) 命令,搜索 src 目录下所有以 .cpp 结尾的文件,并将它们的路径存入变量 SRC
  • GLOB 是 CMake 提供的文件搜索命令,用于匹配文件路径。
  • 示例:如果 src 目录下有 car.cpp 和 train.cpp,那么 SRC 的值将是:
/path/to/src/car.cpp
/path/to/src/train.cpp

include_directories(${PROJECT_SOURCE_DIR}/include) 

作用

  • 将 include 目录(即 car.h 和 train.h 所在目录)添加到项目的头文件搜索路径中。
  • 等效于将 -I/path/to/include 传递给编译器。
  • 这样,源文件中可以直接使用,而无需写完整路径:
#include "car.h"
#include "train.h"

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)

作用

  • 指定生成的库文件的输出目录为项目根目录下的 a 子目录。
  • 如果没有这一行,CMake 默认将库文件输出到构建目录中。

add_library(trans STATIC ${SRC})

作用

  • 定义一个名为 trans 的静态库(STATIC)。
  • SRC 中列出的源文件(如 car.cpp 和 train.cpp)将被编译,并打包成静态库。
8.1.3.2 总结

生成静态库的基本步骤:

# 1. 设置 CMake 最低版本
cmake_minimum_required(VERSION 3.20)
# 2. 定义项目名称和语言
project(MyStaticLibrary LANGUAGES CXX)

# 3. 添加头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 4. 查找源文件
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)

# 5. 设置库文件的输出路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)

# 6. 添加静态库 声明一个静态库目标(STATIC 表示静态库),指定构成静态库的源文件。
add_library(MyStaticLib STATIC ${SRC_FILES})

8.1.4 生成动态库

生成动态库与生成静态库的步骤类似,只需要将添加静态库的STATIC改为SHARED(动态库也叫共享库)

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/so)
add_library(trans SHARED ${SRC})

8.2 CMake调用动静态库

8.2.1 静态库调用流程:

  1. 指定静态库的路径
  2. 添加可执行文件
  3. 链接静态库
  4. 添加库的头文件路径
    假设当前目录如下:
flash@VM-12-10-ubuntu:~/2024/12/14_cmake/useLib$ tree -L 2
.
├── CMakeLists.txt
├── include
│   ├── car.h
│   └── train.h
├── Lib
│   ├── libtrans.a
│   └── libtrans.so
└── main.cpp

如果已经生成了动静态库,可以直接删除src目录,但是建议保留include目录,下面来看CMakeLists.txt的具体编写:

cmake_minimum_required(VERSION 3.20.0)
project(MyProject CXX)

# 指定静态库的路径
set(STATIC_LIB_PATH ${PROJECT_SOURCE_DIR}/Lib/libtrans.a)
# 添加可执行文件
add_executable(MyExecutable main.cpp)
# 链接静态库(仅作用于 MyExecutable)
target_link_libraries(MyExecutable PRIVATE ${STATIC_LIB_PATH})
# 添加库的头文件路径(仅作用于 MyExecutable)
target_include_directories(MyExecutable PRIVATE ${PROJECT_SOURCE_DIR}/include)

8.2.2 动态库调用流程:

  1. 指定动态库的路径
  2. 添加可执行文件
  3. 链接静态库
  4. 添加库的头文件路径
cmake_minimum_required(VERSION 3.20.0)
project(MyProject CXX)

# 指定动态库的路径
set(DYNAMIC_LIB_PATH ${PROJECT_SOURCE_DIR}/Lib/libtrans.so)
# 添加可执行文件
add_executable(MyExecutable main.cpp)
# 链接动态库(仅作用于 MyExecutable)
target_link_libraries(MyExecutable PRIVATE ${DYNAMIC_LIB_PATH})
# 添加库的头文件路径(仅作用于 MyExecutable)
target_include_directories(MyExecutable PRIVATE ${PROJECT_SOURCE_DIR}/include)
12-15 19:53