cmake学习day01
基本起点
本笔记的主要参考文献是cmake文档,对文档的二次提炼和补充学习。
1. cmake_minimum_required()
任何项目的最顶层CMakeLists.txt
都必须首先使用 cmake_minimum_required()
命令指定最低 CMake 版本。这建立策略设置并确保以下 CMake 函数以兼容的 CMake 版本运行。
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
要求 CMake 的最低版本,确保运行的 CMake 版本不低于该版本,否则会停止处理项目并报告错误。
<min>
:必需的,指定项目的 CMake 最低版本,格式为major.minor[.patch[.tweak]]
,例如 3.12、3.12.1 等。major
是主版本号。minor
是次版本号。patch
是补丁版本号(可选)。tweak
是修订版本号(可选)。
[...<policy_max>]
:可选的,从 CMake 3.12 开始支持,用于指定策略版本的上限,格式同<min>
。如果指定,必须至少为<min>
版本。在旧版本 CMake 中会被忽略。[FATAL_ERROR]
:可选的,CMake 2.6 及更高版本接受但忽略该选项。在 CMake 2.4 及更低版本中,若指定此选项,当运行的 CMake 版本低于<min>
时会报错,而不是仅发出警告。
示例
cmake_minimum_required(VERSION 3.31)
2. add_executable()
使用指定的源文件向项目添加可执行文件。分为三类:普通可执行文件,普通可执行文件,别名可执行文件。
普通可执行文件
add_executable(<name> <options>... <sources>...)
add_executable(可执行文件名 源文件1 源文件2 ...)
添加一个名为<name>
的 可执行 目标,该目标将从命令调用中列出的源文件构建。
WIN32
自动设置WIN32_EXECUTABLE
目标属性。 在 Windows 上构建一个带有 WinMain 入口点的可执行文件。当此属性设置为 true 时,在 Windows 上链接的可执行文件将使用 WinMain() 入口点而不是 main() 创建。 这使其成为 GUI 可执行文件而不是控制台应用程序。简单说 Windows 平台上生成“窗口程序”而非控制台程序(即不会弹出命令行黑框)。MACOSX_BUNDLE
自动设置MACOSX_BUNDLE
目标属性。 (在 macOS 或 iOS 上将可执行文件构建为应用程序包,本次学习忽略)。EXCLUDE_FROM_ALL
自动设置EXCLUDE_FROM_ALL
目标属性。 将此目标属性设置为 true(或 false)值,以将目标从包含目录及其祖先目录的“all”目标中排除(或包含)。如果排除,例如在包含目录或其祖先目录中运行 make 将默认不会构建该目标。简而言之就是排除目标项。<name>
对应于逻辑目标名称,并且在项目中必须是全局唯一的。- CMake 默认会把构建的可执行程序放在
cmake-build-debug
目录中。
导入的可执行文件
add_executable(<name> IMPORTED [GLOBAL])
参数 | 含义 |
---|---|
<name> | 你给这个外部可执行文件起的内部名字(目标名) |
IMPORTED | 说明这个目标是“外部导入的”,不由当前项目编译生成 |
GLOBAL | (可选)使该目标在所有子目录中都可见 |
- 使用场景:你已经有 .exe / .out 可执行文件,希望在构建流程中用它。
别名可执行文件
add_executable(<name> ALIAS <target>)
CMake 中的 目标别名(alias target)功能,虽然不像 IMPORTED 那样常见,但在模块化和大型项目中非常有用。假设你有个库或者程序,它的目标名很长、或者跟目录强绑定、或者不方便跨目录引用,CMake 提供 ALIAS 语法让你起个简单名字、跨模块引用。
示例:
add_executable(myproject_internal_app app.cpp)
# 给这个内部目标起个别名
add_executable(MyApp ALIAS myproject_internal_app)
- 使用 ALIAS 的条件
- 只能为已经定义好的目标起别名:所以你不能先写
add_executable(MyApp ALIAS something)
,必须something
已经存在。 - ALIAS 是只读的:你不能修改别名目标的属性。
- ALIAS 主要用于公共接口:尤其是在
add_subdirectory()、install(EXPORT)、
模块封装中,用于隐藏实现细节,导出简洁的接口。
- 只能为已经定义好的目标起别名:所以你不能先写
3.project()
设置项目名称。
project(<PROJECT-NAME> [<language-name>...])
参数 | 含义 |
---|---|
<PROJECT-NAME> | 项目的名字 |
<language-name> | 项目使用的编程语言,如 C , CXX , Fortran 等 |
示例: |
project(MyApp CXX)
- 项目名叫 MyApp
- 使用 C++ 编译器(CXX)
- CMake 会自动启用 C++ 支持,并检查环境是否具备对应编译器
project(<PROJECT-NAME>[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]][DESCRIPTION <project-description-string>][HOMEPAGE_URL <url-string>][LANGUAGES <language-name>...])
关键词 | 说明 |
---|---|
VERSION | 项目的版本号,支持最多四段(如 1.2.3.4 ) |
DESCRIPTION | 项目的简介说明 |
HOMEPAGE_URL | 项目的主页地址(可以是 GitHub 链接等) |
LANGUAGES | 显式设置支持的语言,如 C CXX ,可省略(自动推断) |
- 可以指定多个语言支持
project(MyProject LANGUAGES C CXX)
4.基本起点包含函数各种情况练习
练习 1:最基础的情况
项目结构
Cmake_learn/
├── CMakeLists.txt
└── main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(Cmake_learn)
set(CMAKE_CXX_STANDARD 17)
add_executable(Cmake_learn main.cpp)
main.cpp
#include <iostream>
int main() {auto lang = "C++";std::cout << "Hello and welcome to " << lang << "!\n";for (int i = 1; i <= 5; i++) {std::cout << "i = " << i << std::endl;}return 0;}
练习 2:指定项目版本号与描述
cmake_minimum_required(VESSION 3.31)
project(Cmake_learn CXX) #设定项目名称和语言
set(CMAKE_CXX_STANDARD 17) #设置C++标准为C++17
add_executable(Cmake_learn main.cpp) #添加可执行文件,指定源文件
main.cpp
文件同练习1。
练习 3:添加多个源文件的可执行目标
//main.cpp
#include <iostream>
#include "add.h"
int main() {std::cout << "Add: " << add(2, 3) << "\n";return 0;
}
//add.cpp
#include "add.h"
int add(int a, int b) { return a + b; }
//add.h
int add(int a, int b);
#倘若main.cpp、 add.cpp、add.h在同一目录下
#CMakeList.txt
cmake_minimum_required(VERSION 3.31)
project(Cmake_learn)
set(CMAKE_CXX_STANDARD 17)
add_executable(MathApp main.cpp add.cpp)#为规范代码习惯,最好将头文件和源文件分开存放。#CMakeList.txt
cmake_minimum_required(VERSION 3.31)
project(Cmake_learn)
set(CMAKE_CXX_STANDARD 17) #设置C++标准为C++17
add_executable(Cmake_learn src/main.cpp src/add.cpp) #添加可执行文件,指定源文件x
target_include_directories(Cmake_learnPRIVATE ${CMAKE_SOURCE_DIR}/include
)
target_include_directories
后面详细学习介绍,这里先记住大概格式。
练习 4:使用 IMPORTED 声明外部工具
cmake_minimum_required(VERSION 3.14)
project(CodeGenDemo)# 声明已存在的外部可执行程序
add_executable(CodeGen IMPORTED GLOBAL)
set_target_properties(CodeGen PROPERTIESIMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/tools/CodeGen.exe"
)# 假设 CodeGen.exe 生成一个 cpp 文件
add_custom_command(OUTPUT generated.cppCOMMAND CodeGenCOMMENT "Generating source code..."
)add_executable(MainApp generated.cpp)
add_dependencies(MainApp CodeGen)
练习 5:使用 ALIAS 简化内部目标命名
cmake_minimum_required(VERSION 3.10)project(CoreAliasDemo)# 真正的可执行程序
add_executable(core_exe main.cpp)# 给它起一个别名
add_executable(CoreRunner ALIAS core_exe)# 可以用别名参与链接、依赖等
add_custom_command(TARGET CoreRunnerPOST_BUILDCOMMAND CoreRunnerCOMMENT "Running CoreRunner post-build step"
)
5.configure_file()
configure_file(<input> <output> [@ONLY] [NEWLINE_STYLE [UNIX|DOS|WIN32]])
将一个模板文件(输入)拷贝为一个输出文件,并在其中替换 ${}
或 @VAR@
形式的变量。
没学会,项目不太用得上,暂时搁置后面补。
6.使用 file(GLOB) 动态获取文件
file(GLOB SOURCES "src/*.cpp") # 匹配 src 目录下所有 .cpp 文件
add_executable(Cmake_learn ${SOURCES})
优点:无需手动维护文件列表。
缺点:如果 src 目录下没有 .cpp 文件,仍会报错。
若文件嵌套在多级目录中,可使用递归匹配:
file(GLOB_RECURSE SOURCES "src/**/*.cpp") # 匹配 src 及子目录中所有 .cpp 文件
GLOB
:只在当前目录及其指定的子目录中搜索文件,不会递归地搜索所有子目录。例如,file(GLOB files "src/*.cpp")
会搜索 src 目录下所有扩展名为.cpp
的文件,但不会搜索src
目录的子目录。GLOB_RECURSE
:会递归地搜索当前目录及其所有子目录,包括多级嵌套的子目录。例如,file(GLOB_RECURSE files "src/*.cpp")
会搜索src
目录及其所有子目录下所有扩展名为.cpp
的文件。GLOB
:适用于项目结构简单、文件分布较集中且不需要递归搜索的场景。GLOB_RECURSE
:适用于项目结构复杂、文件分布在多个子目录中,需要全面搜索的场景。
7.target_include_directories
向目标添加包含目录。用于为某个 目标(target) 添加头文件搜索路径,让编译器在编译该目标时知道去哪里找 .h 或 .hpp 文件。
target_include_directories(<target>[SYSTEM] [AFTER|BEFORE]<INTERFACE|PUBLIC|PRIVATE> [items...]
)
参数 | 说明 |
---|---|
<target> | 你要设置的目标,比如 MyApp 或 MyLibrary |
INTERFACE | 只对依赖该 target 的目标生效(自己不需要) |
PUBLIC | 自己和依赖者都需要这个头文件路径 |
PRIVATE | 仅自己需要这个头文件路径 |
SYSTEM | 表示这是系统头文件路径,编译器不会警告其中的内容 |
AFTER / BEFORE | 控制添加顺序(极少使用) |
练习示例
//hello.h
#ifndef HELLO_H
#define HELLO_H
void say_hello();
#endif //HELLO_H
//hello.cpp
#include <iostream>
#include "hello.h"void say_hello() {std::cout << "Hello from say_hello()!" << std::endl;
}
//main.cpp
#include "hello.h"int main() {say_hello();return 0;
}
CMakeList.txt
cmake_minimum_required(VERSION 3.10)
project(CodeGenDemo LANGUAGES CXX)# 创建可执行程序
add_executable(CodeGenDemosrc/main.cppsrc/hello.cpp
)# 添加头文件搜索路径
target_include_directories(CodeGenDemoPRIVATE ${CMAKE_SOURCE_DIR}/include
)
也可以用file
进阶编写cmakeLists:
cmake_minimum_required(VERSION 3.14)
project(CodeGenDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS"${CMAKE_SOURCE_DIR}/src/*.cpp""${CMAKE_SOURCE_DIR}/include/*.h"
)
add_executable(CodeGenDemo ${SOURCES})
target_include_directories(CodeGenDemo PRIVATE ${CMAKE_SOURCE_DIR}/include)
GLOB_RECURSE
:会递归地搜索当前目录及其所有子目录,包括多级嵌套的子目录。CONFIGURE_DEPENDS
:如果文件系统发生了变化(新增或删除了文件),下次重新 cmake 配置时自动重新扫描这个GLOB
。没有这个选项的话,哪怕你新加了.cpp
文件,CMake 也不会知道,除非你手动删掉build/
目录重新配置。
字符串 | 含义 |
---|---|
${CMAKE_SOURCE_DIR} | 当前项目的根目录(CMakeLists.txt 所在) |
/src/*.cpp | 查找 src/ 目录下以及其子目录中所有 .cpp 文件 |
/include/*.h | 查找 include/ 目录下以及其子目录中所有 .h 文件 |