文档结构  
翻译进度:24%     翻译赏金:0 元 (?)    ¥ 我要打赏

Visual Studio 2017对于本地化开发的一个重要变化是对cmake的支持。CMAKE是一个跨平台的开源工具,用于独立于编译器和环境为本机应用程序定义构建过程。cmake使用脚本(称为cmakelists.txt)生成特定于环境的构建文件,例如Visual Studio项目、Xcode项目、生成脚本和其他。尽管cmake支持所有版本的Visual Studio,但从版本6开始,Visual Studio 2017是支持cmake的第一个版本。这意味着可以在VisualStudio 2017中创建、编辑、构建、运行和调试C++代码,而不需要显式生成VC++项目,因为要考虑到不同的系统环境。

第 1 段(可获 1.59 积分)

本文通过一系列示例介绍了CMAKE,以帮助作为VC++开发人员创建和维护CMAKE项目。

对Visual Studio 2017支持的概述

尽管本文是对cmake的介绍,但从Visual Studio中对其支持的简单介绍开始是有意义的。有关支架的深入讨论,请参阅在Visual Studio中支持CMake。

在VisualStudio 2017中,可以通过使用打开文件夹(安装了cmake for vc++组件)来打开C++代码。为此,您需要一个cmake脚本(通常称为cmakelists.txt),与源文件位于同一位置。如果代码跨多个项目和子目录拆分,则需要在每个项目目录中都有一个。要从这样的目录中打开代码,请转到 文件>打开>文件夹。

第 2 段(可获 1.7 积分)

打开文件夹时,可以看到该文件夹的内容,包括解决方案资源管理器中的cmakelists.txt文件。

主菜单包含一个名为cmake的附加项,它具有不同的命令,例如构建脚本、管理cmake缓存、更改cmake设置等等。在cmakelists.txt文件的上下文菜单中,还可以使用不同的命令,例如生成和调试代码。

构建cmake脚本时,将在临时位置创建VC++项目文件,然后由Visual Studio使用这些文件来构建、运行和调试代码。

第 3 段(可获 1.26 积分)

定义示例

在本文的其余部分中,我们将讨论如何构建在几个项目中分组的C++代码:静态库、动态库和应用程序。静态库称为libutil,包含实用程序函数(random.h和random.cpp)。它与称为sharedmod(pisym.h和pisym.cpp)的动态链接库静态链接,后者又与称为theapp(main.cpp)的主应用程序链接。

初始文件夹结构如下:

cmakedemo/
├── include/
|   └── libdef.h
└── src/
    ├── libutil/
    |   ├── random.h
    |   └── random.cpp
    ├── sharedmod/
    |   ├── pisym.h
    |   └── pisym.cpp
    └── theapp/
        └── main.cpp
第 4 段(可获 1.04 积分)

源代码位于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
第 5 段(可获 0.58 积分)

CMake版本

通过使用cmake_minimum_required command 命令(在脚本的开头),您可以使用cmakeLists.txt脚本以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,而不是更高版本。否则,生成将失败,并出现错误:

第 6 段(可获 2.63 积分)
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 name

You specify a name and optionally version and language for an entire project using the project command.

project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])
project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [LANGUAGES <language-name>...])
第 7 段(可获 0.24 积分)

When you build the script, that will become the name of the Visual Studio solution.

project(cmakedemo)

Build messages

It is possible to show message to the user during the building of the scripts by using the message command.

message([<mode>] "message to display" ...)

This takes an optional mode, indicating the type of message, and the text to output. The possible modes are shown in the following table. Notice these are case sensitive and must be written with upper cases.

(none)Important information
STATUSIncidental information
WARNINGCMake Warning, continue processing
AUTHOR_WARNINGCMake Warning (dev), continue processing
SEND_ERRORCMake Error, continue processing, but skip generation
FATAL_ERRORCMake Error, stop processing and generation
DEPRECATIONCMake Deprecation Error or Warning if variable CMAKE_ERROR_DEPRECATED or CMAKE_WARN_DEPRECATED is enabled, respectively, else no message.




第 8 段(可获 1.69 积分)

The command-line cmake tool displays the STATUS messages on stdout and all the others on stderr.

message(STATUS "Setting MSVC flags")

Setting variables

Build variables, cache and environment variables can all be set with the same command, simply called set. The syntax is similar for all these three types of variables. The variable name comes first, then the value and then additional possible arguments. To refer to a variable you must enclose it between ${}, such in ${VARIABLE_NAME}. In case of environment variables, they must be specified within ENV{}, such as in ENV{PATH}.

第 9 段(可获 1.01 积分)

Cache variables are meant for user settable value, but this command does not overwrite existing cache variables unless you specify the FORCE option. When you set a cache variable, you also need to specify a type, which can be one of BOOL, FILEPATH (path to a file), PATH (path to a directory), STRING or INTERNAL, and a string that describes the variable, which is shown in the GUI version of the tool.

set(<variable> <value>... [PARENT_SCOPE])
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(ENV{<variable>} <value>...)

There are pre-defined variables that are used for various purposes. In our top-level script, we will use some of these:

第 10 段(可获 1.08 积分)
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")
第 11 段(可获 0.76 积分)

Note: To the specified output directory, CMake implicitly adds the name of the configuration, such as Debug or Release. Therefore, if you specify the output to be C:\Work\Learn\cmakedemo\bin\, the actual output folder for the Debug configuration will be C:\Work\Learn\cmakedemo\bin\Debug\.

Variables can be set from the command line. You need to introduce them with the -D prefix and then provide a value.

cmake -DCMAKE_RUNTIME_OUTPUT_DIRECTORY c:\demo\bin

Adding subdirectories

When your code is organized in multiple directories, you can add subdirectories to the build with the command add_subdirectory.

第 12 段(可获 1.15 积分)
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

The only mandatory argument is a directory name or path. This can be either an absolute path, or a relative path, in which case it is resolved starting with the current output directory. This directory must contain a CMakeLists.txt script, which will be processed immediately by the CMake, before it continues with the current input script.

add_subdirectory(libutil)
add_subdirectory(sharedmod)
add_subdirectory(theapp)

Adding libraries and executables

You can add libraries and executables to the build using the add_library and add_executable commands. These two have similar syntax:

第 13 段(可获 1.06 积分)
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

<name> is the name of the target to be built from the list of specified sources. It is a logical target name and must be unique within the project. The actual filename of the library or executable is based on this name but depends on the native platform. For library files it is <name>.lib on Windows and lib<name>.a on other systems, and for executable names are <name>.exe on Windows and <name> on other systems.

第 14 段(可获 0.86 积分)

For libraries, STATIC (archives of object files used for linking other targets), SHARED (dynamically linked libraries) or MODULE (plugins not linked into other targets but loaded dynamically at runtime) can be specified to indicate the type of the library. If none of these is specified, then STATIC or SHARED are implied, depending whether the variable BUILD_SHARED_LIBS is ON or OFF.

The list of source files may contain both source and header files. Note that only the files listed with the command show up in the project when you generate one for Visual Studio.

第 15 段(可获 1.13 积分)
add_library(libutil STATIC random.cpp random.h)

If the number of files it too large to specify manually, or you want to bulk include them based on a pattern, you can use the file command to generate a list of files that match a pattern and store it in a variable that you later refer in the add_library or add_executable command. The following example defines a variable called headers that contains all the files in the current folder that have the extension .h.

file(GLOB headers *.h)
add_library(sharedmod SHARED pisym.cpp ${headers})

add_executable(theapp main.cpp)
第 16 段(可获 0.91 积分)

Including and linking directories

Directories can be added to the list of include directories using the command include_directories and to the list of linking directories (where the linker looks for libraries) using the command link_directories.

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

link_directories(directory1 directory2 ...)

The directory or directories specified with the include_directories command are added to the INCLUDE_DIRECTORY directory property for the current CMakeLists.txt script and all the INCLUDE_DIRECTORY target property for each target in the current CMakeLists.txt script. When SYSTEM is specified, the directory will be designated as a system include directory with the compiler.

第 17 段(可获 1.11 积分)
include_directories(${CMAKE_SOURCE_DIR}/../include)

The directories specified with the link_directories are applied only to targets created after the command. Therefore it is important to use this command before add_library or add_executable.

link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY})

Preprocessor definitions

Preprocessor definitions can be added with the add_definitions command. The name must be prefixed with -D.

add_definitions(-DEXPMODULE)

Adding library dependencies

You can specify library dependencies or linking flags for a target using the command target_link_libraries. This command has multiple forms, but in this article we will discuss only one:

第 18 段(可获 0.99 积分)
target_link_libraries(<target> ... <item>... ...)

<target> must have been previously created with either add_library() or add_executable() and multiple targets can be specified. <item> can be a library name, a full path to a library file, a plain library name, a link flag or a debug/optimized/general keyword followed by another item. The keyword debug refers to the debug configuration, optimized to all other configurations, and general to all configurations and is optional.

The use of debug and optimized keywords preceding an item is useful for specifying different targets for different configurations, for instance linking with a library called mylibrary.lib for a release configuration and mylibraryd.lib for a debug configuration.

第 19 段(可获 1.19 积分)
target_link_libraries(sharedmod debug libutil.lib)
target_link_libraries(sharedmod optimized libutil.lib)

Setting build dependencies

The build order of the targets is usually important, as some targets must be built before others. Dependencies between targets in terms of build order can be specified with the add_dependencies command.

add_dependencies(<target> [<target-dependency>]...)

All the targets specified with this command must be top level targets created with add_executable(), add_library() or add_custom_target().

add_dependencies(sharedmod libutil)
add_dependencies(theapp sharedmod)
第 20 段(可获 0.65 积分)

Setting properties

You can set values for named properties in a given scope using the set_property command. This command takes an optional scope and a mandatory PROPERTY optione followed by the name of the property and its value or values.

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 ...]])
第 21 段(可获 0.51 积分)

We can use this command the set the startup project in a Visual Studio solution. For instance, we if we want the application theapp to be the startup project we must specify the following command in the top level CMakeLists.txt script.

set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT theapp)

Putting it all together

With all these commands discussed we can put together the scripts for the main folder and each of the sub-folders, as shown below.

  • Top-level CMakeLists.txt script:
    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 script:
    add_library(libutil STATIC random.cpp random.h)
    
    include_directories(${CMAKE_SOURCE_DIR}/../include)
    add_definitions(-DEXPMODULE)
  • sharedmod CMakeLists.txt script:
    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 script:
    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)
第 22 段(可获 1.06 积分)

Building the scripts explicitly

Although Visual Studio 2017 builds the CMake scripts automatically (or on demand), you can still use CMake without Visual Studio to generate Visual C++ projects and solutions. The following command shows how to use CMake for this purpose, assuming the code is located under the src folder (as shown in the beginning of the article).

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
第 23 段(可获 0.74 积分)

"Visual Studio 15 2017" is the name of the generator that produces Visual Studio 2017 projects targeting the x86 platform. If you need to target the x64 or the ARM platforms, then you must use other generators, "Visual Studio 15 2017 Win64" and "Visual Studio 15 2017 ARM" respectively.

There are several important differences concerning the generated projects when using a regular CMake version or the one distributed with Visual Studio.

  • CMake generates configurations that target a single platform, x86, x64 or ARM, depending on the generator that you specify, while the Visual Studio distributed CMake generates configurations for both x86 and x64, but not also ARM.
  • CMake generates four configurations, Debug, Release, MinSizeRel, RelWithDebInfo (the last three being Release configurations), while the Visual Studio distributed CMake generates a Debug and a Release configuration, but for both targets, therefore also a total of four configurations x86-Debug, x86-Release, x64-Debug and x64-Release.

  • CMake generates the projects in the current working directory, while the Visual Studio distributed version generates them in an AppData\Local folder as shown earlier. In both cases however, the output is in the same location, which is the one specified with pre-defined variables, such as CMAKE_RUNTIME_OUTPUT_DIRECTORY, as shown above.

第 24 段(可获 2.49 积分)

Building cross-IDE and platform

Although this articles is focused on Visual Studio, the primary reason for using CMake is building cross-platform and cross-compiler applications. The same scripts we created for creating Visual Studio projects can be used for other IDEs. For instance, just by replacing the generator from "Visual Studio 15 2017" with Xcode, CMake will create an Xcode project.

cmake -G Xcode src

NOTE: In order to successfully build the project, the source code and the scripts require some changes though. For instance std::optional used in the sample code is not supported in the LLVM toolchain available with the latest version of Xcode available at the time of writing this article. Also, the compiler options /EHc /std:c++latest should be replaced with -std=c++1z -fexceptions -g -Wall. Other changes are necessary, but these are beyond the scope of this article.

第 25 段(可获 1.6 积分)

文章评论