面向 Visual C++ 开发人员的 CMake 教程






4.98/5 (22投票s)
本文通过一系列示例,旨在为 VC++ 开发人员介绍 CMake,帮助您创建和维护 CMake 项目。
引言
Visual Studio 2017 对原生开发最重要的变化之一就是支持 CMake。CMake 是一个跨平台开源工具,用于独立于编译器和环境定义原生应用程序的构建过程。CMake 使用脚本(称为 CMakeLists.txt)来生成特定于环境的构建文件,例如 Visual Studio 项目、XCode 项目、make 脚本等。尽管 CMake 从 6.0 版本起就支持所有 Visual Studio 版本,但 Visual Studio 2017 是第一个支持 CMake 的版本。这意味着在 Visual Studio 2017 中,您无需显式生成 VC++ 项目即可创建、编辑、构建、运行和调试 C++ 代码,因为系统会在后台自动处理这些操作。
本文通过一系列示例,旨在为 VC++ 开发人员介绍 CMake,帮助您创建和维护 CMake 项目。
Visual Studio 2017 支持概览
虽然本文是 CMake 的入门介绍,但我们还是有必要先简要介绍一下 Visual Studio 中对 CMake 的支持。关于支持的详细讨论,请参阅 CMake 支持在 Visual Studio 中。
在 Visual Studio 2017 中,您可以使用 打开文件夹 功能来打开 C++ 代码。为此,您需要在源代码所在的同一位置拥有一个 CMake 脚本,该脚本始终命名为 CMakeLists.txt。如果您的代码分布在多个项目和子目录中,则每个项目目录中都需要有一个。要从此类目录中打开代码,请转到 **文件** > **打开** > **文件夹**。
打开文件夹后,您可以在解决方案资源管理器中看到该文件夹的内容,包括 CMakeLists.txt 文件。
主菜单中会有一个名为 CMake 的附加项,其中包含不同的命令,例如构建脚本、管理 CMake 缓存、更改 CMake 设置等。构建和调试代码等不同命令也可通过 CMakeLists.txt 文件的上下文菜单获得。
当您构建 CMake 脚本时,VC++ 项目文件会被创建并存储在临时位置,然后 Visual Studio 使用这些文件来构建、运行和调试代码。
定义示例
在本文的其余部分,我们将讨论如何构建分组在多个项目中的 C++ 代码:一个静态库、一个动态库和一个应用程序。静态库名为 libutil,包含实用函数(random.h 和 random.cpp)。它与名为 sharedmod
的动态链接库(pisym.h 和 pisym.cpp)静态链接,而 sharedmod
又与名为 theapp
的主应用程序(main.cpp)链接。
初始文件夹结构如下所示
cmakedemo/
├── include/
| └── libdef.h
└── src/
├── libutil/
| ├── random.h
| └── random.cpp
├── sharedmod/
| ├── pisym.h
| └── pisym.cpp
└── theapp/
└── main.cpp
源代码位于 src 目录下方,每个项目都有自己的子目录。还有一个 include 目录,与 src 处于同一级别。需要在每个 src 子文件夹以及根目录中创建 CMakeLists.txt 脚本。
cmakedemo/
├── include/
| └── libdef.h
└── src/
├── libutil/
| ├── random.h
| ├── random.cpp
| └── CMakeLists.txt
├── sharedmod/
| ├── pisym.h
| ├── pisym.cpp
| └── CMakeLists.txt
├── theapp/
| ├── main.cpp
| └── CMakeLists.txt
└── CMakeLists.txt
CMake 版本
您可以使用 CMakeLists.txt 脚本通过 cmake_minimum_required 命令(位于脚本开头)来指定最低 CMake 版本。
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] [FATAL_ERROR])
如果安装的 CMake 版本低于指定的版本,脚本的解析将以错误停止。此命令是可选的,但如果存在,则应放在顶层脚本的开头,在任何其他命令之前。
cmake_minimum_required(VERSION 3.6.2)
Visual Studio 2017 自带自己的 CMake 分发版。它位于文件夹 c:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO\2017\ENTERPRISE\COMMON7\IDE\COMMONEXTENSIONS\MICROSOFT\CMAKE\CMake\ (假设 Visual Studio 的默认安装位置)。Visual Studio 2017 RTM 随附的版本是 3.6.20160606-g0391f-MSVC,这基本上是支持 Visual Studio 2017 的 3.6.2 版本。您也可以有其他 CMake 安装。例如,如果您从 cmake.org 下载并安装在默认位置,那么它也位于 c:\Program Files\CMake\。当从 Visual Studio 运行时,始终使用的是 VS 分发版。因此,您必须将最低版本指定为 3.6.2,而不是更高的版本。否则,构建将因错误而失败。
1> Working directory: C:\Users\M\AppData\Local\CMakeBuild\
bfe38435-a2ed-1738-925f-b2cbf11bf883\build\x86-Debug
1> CMake Error at C:\Work\Learn\cmakedemo\src\CMakeLists.txt:1 (cmake_minimum_required):
1> CMake 3.7 or higher is required. You are running version
1> 3.6.20160606-g0391f-MSVC
项目名称
您可以使用 project 命令指定整个项目的名称,以及可选的版本和语言。
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[LANGUAGES <language-name>...])
构建脚本时,该名称将成为 Visual Studio 解决方案的名称。
project(cmakedemo)
构建消息
可以使用 message 命令在构建脚本期间向用户显示消息。
message([<mode>] "message to display" ...)
这需要一个可选的 mode
参数,指示消息的类型,以及要输出的文本。可能的模式如上表所示。请注意,这些模式区分大小写,必须用大写字母书写。
(无) | 重要信息 |
STATUS | 附带信息 |
警告 | CMake 警告,继续处理 |
AUTHOR_WARNING | CMake 警告(dev),继续处理 |
SEND_ERROR | CMake 错误,继续处理,但跳过生成 |
FATAL_ERROR | CMake 错误,停止处理和生成 |
DEPRECATION | 如果分别启用了 CMAKE_ERROR_DEPRECATED 或 CMAKE_WARN_DEPRECATED 变量,则会显示 CMake 弃用错误或警告,否则不显示任何消息。 |
命令行 cmake 工具将 STATUS
消息显示在 stdout
上,而所有其他消息都显示在 stderr
上。
message(STATUS "Setting MSVC flags")
设置变量
构建变量、缓存变量和环境变量都可以使用同一个命令设置,该命令称为 set。这三种变量的语法相似。首先是变量名,然后是值,最后是可能的附加参数。要引用变量,必须将其用 ${}
包围,例如 ${VARIABLE_NAME}
。对于环境变量,必须将其用 ENV{}
包围,例如 ENV{PATH}
。
缓存变量用于用户可设置的值,但此命令不会覆盖现有缓存变量,除非您指定 FORCE
选项。设置缓存变量时,您还需要指定一个类型,该类型可以是 BOOL
、FILEPATH
(文件路径)、PATH
(目录路径)、STRING
或 INTERNAL
之一,以及一个描述变量的 string
,该描述将显示在工具的 GUI 版本中。
set(<variable> <value>... [PARENT_SCOPE])
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(ENV{<variable>} <value>...)
有一些预定义的变量用于各种目的。在我们的顶层脚本中,我们将使用其中一些
- CMAKE_CXX_FLAGS 用于指定 VC++ 编译器的标志。
- CMAKE_SOURCE_DIR 指示源代码树顶层的路径。
- CMAKE_ARCHIVE_OUTPUT_DIRECTORY、CMAKE_LIBRARY_OUTPUT_DIRECTORY、CMAKE_RUNTIME_OUTPUT_DIRECTORY 用于初始化存档库和运行时目标的输出目录。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHc /std:c++latest")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin")
注意:CMake 会在指定的输出目录中隐式添加配置的名称,例如 Debug 或 Release。因此,如果您指定的输出为 C:\Work\Learn\cmakedemo\bin\,则 Debug 配置的实际输出文件夹将是 C:\Work\Learn\cmakedemo\bin\Debug\。
可以通过命令行设置变量。您需要使用 -D
前缀来引入它们,然后提供一个值。
cmake -DCMAKE_RUNTIME_OUTPUT_DIRECTORY c:\demo\bin
添加子目录
当您的代码组织在多个目录中时,您可以使用 add_subdirectory 命令将子目录添加到构建中。
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
唯一强制参数是目录名称或路径。它可以是绝对路径,也可以是相对路径,在这种情况下,它会从当前输出目录开始解析。此目录必须包含一个 CMakeLists.txt 脚本,CMake 将在继续处理当前输入脚本之前立即处理该脚本。
add_subdirectory(libutil)
add_subdirectory(sharedmod)
add_subdirectory(theapp)
添加库和可执行文件
您可以使用 add_library 和 add_executable 命令将库和可执行文件添加到构建中。这两个命令的语法相似
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
<name>
是要从指定源列表中构建的目标的名称。这是一个逻辑目标名称,在项目中必须是唯一的。库或可执行文件的实际文件名基于此名称,但取决于本地平台。对于库文件,在 Windows 上是 <name>.lib
,在其他系统上是 lib<name>.a
;对于可执行文件名,在 Windows 上是 <name>.exe
,在其他系统上是 <name>
。
对于库,可以指定 STATIC
(用于链接其他目标的对象的存档)、SHARED
(动态链接库)或 MODULE
(未链接到其他目标但运行时动态加载的插件)来指示库的类型。如果未指定这些类型中的任何一种,则默认为 STATIC
或 SHARED
,具体取决于变量 BUILD_SHARED_LIBS 是 ON
还是 OFF
。
源文件列表可以包含源文件和头文件。请注意,在为 Visual Studio 生成项目时,只有在命令中列出的文件才会显示在项目中。
add_library(libutil STATIC random.cpp random.h)
如果文件数量太多无法手动指定,或者您想基于模式批量包含它们,可以使用 file 命令生成匹配模式的文件列表,并将其存储在一个变量中,然后您可以在 add_library
或 add_executable
命令中引用该变量。以下示例定义了一个名为 headers
的变量,其中包含当前文件夹中所有扩展名为 .h 的文件。
file(GLOB headers *.h)
add_library(sharedmod SHARED pisym.cpp ${headers})
add_executable(theapp main.cpp)
包含和链接目录
可以使用 include_directories 命令将目录添加到包含目录列表中,并使用 link_directories 命令将其添加到链接目录列表(链接器查找库的位置)中。
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
link_directories(directory1 directory2 ...)
使用 include_directories
命令指定的目录会添加到当前 CMakeLists.txt 脚本的 INCLUDE_DIRECTORY
目录属性中,以及当前 CMakeLists.txt 脚本中每个目标的 INCLUDE_DIRECTORY
目标属性中。当指定 SYSTEM
时,该目录将被指定为编译器的系统包含目录。
include_directories(${CMAKE_SOURCE_DIR}/../include)
使用 link_directories
指定的目录仅应用于在此命令之后创建的目标。因此,在 add_library
或 add_executable
之前使用此命令非常重要。
link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
预处理器定义
可以使用 add_definitions 命令添加预处理器定义。名称必须以 -D
作为前缀。
add_definitions(-DEXPMODULE)
添加库依赖项
您可以使用 target_link_libraries 命令为目标指定库依赖项或链接标志。此命令有多种形式,但在本文中,我们只讨论一种
target_link_libraries(<target> ... <item>... ...)
<target>
必须之前已通过 add_library()
或 add_executable()
创建,并且可以指定多个目标。<item>
可以是库名、库文件的完整路径、纯库名、链接标志或 debug
/optimized
/general
关键字后跟另一个项。关键字 debug
指的是调试配置,optimized
指的是所有其他配置,general
指的是所有配置,并且是可选的。
使用 debug
和 optimized
关键字放在项前面,对于为不同配置指定不同目标很有用,例如,为发布配置链接名为 mylibrary.lib 的库,为调试配置链接名为 mylibraryd.lib 的库。
target_link_libraries(sharedmod debug libutil.lib)
target_link_libraries(sharedmod optimized libutil.lib)
设置构建依赖项
目标之间的构建顺序通常很重要,因为某些目标必须在其他目标之前构建。可以通过 add_dependencies 命令指定目标之间的构建顺序依赖项。
add_dependencies(<target> [<target-dependency>]...)
使用此命令指定的所有目标都必须是使用 add_executable()
、add_library()
或 add_custom_target()
创建的顶层目标。
add_dependencies(sharedmod libutil)
add_dependencies(theapp sharedmod)
设置属性
您可以使用 set_property 命令在给定范围内为命名属性设置值。此命令接受一个可选的范围和一个强制的 PROPERTY
选项,后跟属性的名称及其值或值。
set_property(<GLOBAL |
DIRECTORY [dir] |
TARGET [target1 [target2 ...]] |
SOURCE [src1 [src2 ...]] |
INSTALL [file1 [file2 ...]] |
TEST [test1 [test2 ...]] |
CACHE [entry1 [entry2 ...]]>
[APPEND] [APPEND_STRING]
PROPERTY <name> [value1 [value2 ...]])
我们可以使用此命令在 Visual Studio 解决方案中设置启动项目。例如,如果我们希望应用程序 theapp
成为启动项目,我们必须在顶层 CMakeLists.txt 脚本中指定以下命令。
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT theapp)
整合
通过讨论的所有这些命令,我们可以将主文件夹和每个子文件夹的脚本组合起来,如下所示。
- 顶层 CMakeLists.txt 脚本
cmake_minimum_required(VERSION 3.6.2) project(cmakedemo) message(STATUS "Setting MSVC flags") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHc /std:c++latest") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../bin") add_subdirectory(libutil) add_subdirectory(sharedmod) add_subdirectory(theapp) add_dependencies(sharedmod libutil) add_dependencies(theapp sharedmod) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT theapp)
libutil
CMakeLists.txt 脚本add_library(libutil STATIC random.cpp random.h) include_directories(${CMAKE_SOURCE_DIR}/../include) add_definitions(-DEXPMODULE)
sharedmod
CMakeLists.txt 脚本include_directories(${CMAKE_SOURCE_DIR}/libutil) include_directories(${CMAKE_SOURCE_DIR}/../include) link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) file(GLOB headers *.h) add_library(sharedmod SHARED pisym.cpp ${headers}) add_definitions(-DEXPMODULE) target_link_libraries(sharedmod debug libutil.lib) target_link_libraries(sharedmod optimized libutil.lib)
theapp
CMakeLists.txt 脚本include_directories(${CMAKE_SOURCE_DIR}/../include) include_directories(${CMAKE_SOURCE_DIR}/libutil) include_directories(${CMAKE_SOURCE_DIR}/sharedmod) link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) add_executable(theapp main.cpp) target_link_libraries(theapp debug sharedmod.lib) target_link_libraries(theapp optimized sharedmod.lib)
显式构建脚本
尽管 Visual Studio 2017 会自动(或按需)构建 CMake 脚本,但您仍然可以在没有 Visual Studio 的情况下使用 CMake 来生成 Visual C++ 项目和解决方案。以下命令显示了如何为此目的使用 CMake,假设代码位于 src 文件夹下(如本文开头所示)。
cmake -G "Visual Studio 15 2017" src
-- The C compiler identification is MSVC 19.10.25017.0
-- The CXX compiler identification is MSVC 19.10.25017.0
-- Check for working C compiler: C:/Program Files
(x86)/Microsoft Visual Studio/2017/Enterprise/VC/Tools/MSVC/14.10.25017/bin/HostX86/x86/cl.exe
-- Check for working C compiler: C:/Program Files
(x86)/Microsoft Visual Studio/2017/Enterprise/VC/Tools/MSVC/14.10.25017/bin/HostX86/x86/cl.exe
-- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files
(x86)/Microsoft Visual Studio/2017/Enterprise/VC/Tools/MSVC/14.10.25017/bin/HostX86/x86/cl.exe
-- Check for working CXX compiler: C:/Program Files
(x86)/Microsoft Visual Studio/2017/Enterprise/VC/Tools/MSVC/14.10.25017/bin/HostX86/x86/cl.exe
-- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting MSVC flags
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Work/Learn/cmakedemo
"Visual Studio 15 2017" 是生成 Visual Studio 2017 项目(针对 x86 平台)的生成器名称。如果您需要针对 x64 或 ARM 平台,则必须使用其他生成器,分别为 "Visual Studio 15 2017 Win64" 和 "Visual Studio 15 2017 ARM"。
在使用常规 CMake 版本或 Visual Studio 随附的 CMake 版本时,生成的项目存在一些重要的区别。
- CMake 生成针对单个平台(x86、x64 或 ARM,取决于您指定的生成器)的配置,而 Visual Studio 随附的 CMake 则为 x86 和 x64 生成配置,但不包括 ARM。
- CMake 生成四种配置:Debug、Release、MinSizeRel、RelWithDebInfo(后三种是 Release 配置),而 Visual Studio 随附的 CMake 则为 Debug 和 Release 配置生成,但同时针对这两个目标,因此总共生成四种配置:x86-Debug、x86-Release、x64-Debug 和 x64-Release。
- CMake 在当前工作目录中生成项目,而 Visual Studio 随附的版本则在 AppData\Local 文件夹中生成项目,如前所示。但在这两种情况下,输出都位于相同的位置,即使用预定义变量(如
CMAKE_RUNTIME_OUTPUT_DIRECTORY
)指定的位置,如上所示。
跨 IDE 和平台构建
尽管本文专注于 Visual Studio,但使用 CMake 的主要原因是构建跨平台和跨编译器应用程序。我们创建的用于创建 Visual Studio 项目的相同脚本也可用于其他 IDE。例如,只需将生成器从 "Visual Studio 15 2017" 替换为 Xcode,CMake 就会创建一个 Xcode 项目。
cmake -G Xcode src
注意:为了成功构建项目,源代码和脚本还需要进行一些更改。例如,示例代码中使用的 std::optional
在撰写本文时最新版本的 Xcode 提供的 LLVM 工具链中不受支持。此外,编译器选项 /EHc /std:c++latest
应替换为 -std=c++1z -fexceptions -g -Wall
。还有其他更改是必要的,但这些超出了本文的范围。
历史
- 2017 年 4 月 10 日:初始版本