在跨平台cmake项目中设置编译器标志的现代方法

Mik*_*eMB 31 c++ cmake

我想编写一个cmake文件,在调试和发布版本中为clang ++,g ++和MSVC设置不同的编译器选项.我目前正在做的事情看起来像这样:

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++latest /W4")
    # Default debug flags are OK 
    set(CMAKE_CXX_FLAGS_RELEASE "{CMAKE_CXX_FLAGS_RELEASE} /O2")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1z -Wall -Wextra -Werror")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} some other flags")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")

    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
    else()
        # nothing special for gcc at the moment
    endif()
endif()
Run Code Online (Sandbox Code Playgroud)

但我有几个问题:

  1. 首先简单:有没有像relly澳鹏,让我来代替没有命令set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo")append(CMAKE_CXX_FLAGS "Foo")
  2. 我已多次阅读,一个人不应该首先手动设置CMAKE_CXX_FLAGS和类似的变量,但我不知道使用什么其他机制.
  3. 最重要的是:我在这里的方式,我需要为每个编译器和配置单独的构建目录理想情况下,我想将其转换为同一目录中的多个目标,以便我可以调用make foo_debug_clang.

所以我的问题是

  • a)有没有更好的方法来编写解决我的"痛点"的cmake脚本?解决上述问题?
  • b)是否有类似于如何建立此类项目的现代最佳实践?

我在互联网上找到的大多数参考资料要么过时,要么只显示一些简单的例子.我目前正在使用cmake3.8,但如果这有什么不同,我对更新版本的答案更感兴趣.

Ale*_*ing 18

不要这样做!

尤其是在 CMake 3.19+ 中,预设是一个选项。将可选设置(如警告标志)放入预设中,并仅将硬构建要求放入 CMakeLists.txt 中。

请继续阅读以了解原因


我想编写一个 cmake 文件,在调试和发布版本中为 clang++、g++ 和 MSVC 设置不同的编译器选项。

事情是这样的:您不想编写设置不同选项的 CMakeLists.txt,您只是想有一个方便的位置来存储您的标志。这就是预设和工具链文件的用武之地(更多内容见下文)。

我目前正在做的事情看起来像这样:

if(MSVC)
    # ...
else()
    # ...
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        # ...
    else()
        # ...
    endif()
endif()
Run Code Online (Sandbox Code Playgroud)

[...] 我已经读过多次,不应该首先手动设置 CMAKE_CXX_FLAGS 和类似的变量,但我不确定要使用什么其他机制。

这种结构存在的问题如下:

  1. 编译器供应商太多了。是的,有 MSVC、Clang 和 GCC,但还有 Intel 编译器、PGI 编译器等等。
  2. 编译器变体太多。不仅有 Clang,还有 ClangCL 和 Clang CUDA 编译器。Intel编译器还可以在MSVC和GCC兼容模式之间切换。
  3. 编译器版本太多。警告标志的含义因版本而异。从一个版本到下一个版本,给定的警告可能或多或少敏感,尤其是执行数据流分析的更高级的警告。启用“警告作为错误”后,这将为您的用户转化为损坏的构建。

您不想为您的构建维护标志兼容性表。幸运的是,有一个简单的解决方案:不要这样做。

将您想要的标志存储在预设(CMake 3.19+)或工具链文件(CMake 3.0+,可能更早)中,并让您的用户选择加入这些设置(如果他们选择) 。

使用预设,就像编写 CMakePresets.json 文件一样简单。文档中有一些广泛的示例。然后让用户告诉您他们想要使用哪组标志:

# Using presets:
$ cmake --preset=msvc
$ cmake --preset=gcc
$ cmake --preset=clang

# Using toolchains:
$ cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$PWD/cmake/msvc.cmake
$ cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$PWD/cmake/gcc.cmake
$ cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$PWD/cmake/clang.cmake
Run Code Online (Sandbox Code Playgroud)

等等。

最重要的是:我在这里这样做的方式,我需要为每个编译器和配置一个单独的构建目录理想情况下,我想将其转换为在同一目录中拥有多个目标,这样我就可以调用make foo_debug_clang.

这是一个奇怪的要求,因为无论如何,其中每一个都必须完全构建所有内容。老实说,我只是建议设置一个包装器 Makefile 来支持此工作流程,因为 (a) CMake 本身不支持它,并且 (b) 扩展 CMake 来这样做并没有真正的优势。

  • b) 对于如何建立此类项目,是否存在公认的、现代的最佳实践?

我不知道“接受”,但我绝对发现,将硬构建要求与CMakeLists.txt 和预设(或工具链文件)之间的理想构建设置分开,分别解决(或回避)了许多此类问题问题。最终结果是更强大、更易于使用的构建。


Flo*_*ian 14

你的方法 - 正如@Tsyvarev评论的那样 - 绝对没问题,因为你在CMake中要求"新"方法就是你的代码将转化为:

cmake_minimum_required(VERSION 3.8)

project(HelloWorld)

string(
    APPEND _opts
    "$<IF:$<CXX_COMPILER_ID:MSVC>,"
        "/W4;$<$<CONFIG:RELEASE>:/O2>,"
        "-Wall;-Wextra;-Werror;"
            "$<$<CONFIG:RELEASE>:-O3>"
            "$<$<CXX_COMPILER_ID:Clang>:-stdlib=libc++>"
    ">"
)

add_compile_options("${_opts}")

add_executable(HelloWorld "main.cpp")

target_compile_features(HelloWorld PUBLIC cxx_lambda_init_captures)
Run Code Online (Sandbox Code Playgroud)

你拿add_compile_options()- 和@Al.G.已评论 - "使用脏生成器表达式 ".

生成器表达式有一些缺点:

  1. 非常有用的$<IF:...,...,...>表达式仅适用于CMake版本> = 3.8
  2. 你必须把它写在一行.为了避免它,我使用了string(APPEND ...),你也可以用来"优化"你的set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ...电话.
  3. 这很难阅读和理解.例如,需要分号才能使其成为编译选项列表(否则CMake会引用它).

因此,最好使用更具可读性和向后兼容性的方法add_compile_options():

if(MSVC)
    add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
else()
    add_compile_options("-Wall" "-Wextra" "-Werror" "$<$<CONFIG:RELEASE>:-O3>")
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        add_compile_options("-stdlib=libc++")
    else()
        # nothing special for gcc at the moment
    endif()
endif()
Run Code Online (Sandbox Code Playgroud)

是的,您不再明确指定C++标准,只需命名您的代码/目标依赖于调用的C++功能target_compile_features().

对于这个例子,我选择了cxx_lambda_init_captures哪个例如较旧的GCC编译器会出现以下错误(例如,如果编译器不支持此功能会发生什么):

The compiler feature "cxx_lambda_init_captures" is not known to CXX compiler

"GNU"

version 4.8.4.
Run Code Online (Sandbox Code Playgroud)

您需要编写一个包装器脚本来使用"单一配置"makefile生成器构建多个配置,或者使用"多配置"IDE作为Visual Studio.

以下是对示例的引用:

因此,我使用Open FolderVisual Studio 2017 CMake支持对以下内容进行了测试,以便在此示例中结合,编译器:

配置

CMakeSettings.json

{
    // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
    "configurations": [
        {
            "name": "x86-Debug",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "x86-Release",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "Clang-Debug",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "cmakeCommandArgs": "-T\"LLVM-vs2014\"",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "Clang-Release",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "cmakeCommandArgs": "-T\"LLVM-vs2014\"",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "GNU-Debug",
            "generator": "MinGW Makefiles",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "variables": [
                {
                    "name": "CMAKE_MAKE_PROGRAM",
                    "value": "${projectDir}\\mingw32-make.cmd"
                }
            ]
        },
        {
            "name": "GNU-Release",
            "generator": "Unix Makefiles",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "variables": [
                {
                    "name": "CMAKE_MAKE_PROGRAM",
                    "value": "${projectDir}\\mingw32-make.cmd"
                }
            ]
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

mingw32的-make.cmd

@echo off
mingw32-make.exe %~1 %~2 %~3 %~4
Run Code Online (Sandbox Code Playgroud)

因此,您可以在Visual Studio 2017中使用任何CMake生成器,有一些不健康的引用(2017年9月,可能稍后修复)需要mingw32-make.cmd中间人(删除引号).

  • @MikeMB通过设置[`CMAKE_CXX_EXTENSIONS`](https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_EXTENSIONS.html#variable:CMAKE_CXX_EXTENSIONS)来获得更严格的`-std = c ++ XX`选项`OFF`. (2认同)