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

时间:2017-08-30 08:14:11

标签: 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()

但我有几个问题:

  1. 首先是微不足道的:是否真的没有像appen这样的命令可以让我用set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo")取代append(CMAKE_CXX_FLAGS "Foo")
  2. 我已多次阅读,首先不应手动设置CMAKE_CXX_FLAGS和类似的变量,但我不确定使用其他机制。
  3. 最重要的是:我在这里的方式,我需要为每个编译器和配置单独的构建目录理想情况下,我想将其转换为同一目录中的多个目标,以便我可以例如致电make foo_debug_clang
  4. 所以我的问题是

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

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

5 个答案:

答案 0 :(得分:12)

你的方法 - 正如@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)

你拿add_compile_options()和 - 作为@ Al.G。已注释 - &#34;使用脏generator expressions&#34;。

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

  1. 非常有帮助的$<IF:...,...,...>表达式仅适用于CMake版本&gt; = 3.8
  2. 你必须把它写在一行。为了避免这种情况,我使用了string(APPEND ...),您也可以使用它来优化&#34;您的set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ...来电。
  3. 难以阅读和理解。例如。需要分号才能使其成为编译选项列表(否则CMake会引用它)。
  4. 因此,最好使用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()
    

    是的,您不再明确指定C ++标准,只需将C++ feature命名为您的代码/目标取决于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.
    

    您需要编写一个包装器脚本来使用"single configuration" makefile generator构建多个配置,或者使用"multi configuration" IDE作为Visual Studio。

    以下是对示例的引用:

    所以我使用Open Folder Visual Studio 2017 CMake支持对以下内容进行了测试,以便在此示例中合并编译器:< / p>

    Configurations

    <强> 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"
                    }
                ]
            }
        ]
    }
    

    <强>的mingw32-make.cmd

    @echo off
    mingw32-make.exe %~1 %~2 %~3 %~4
    

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

答案 1 :(得分:3)

解决前两点,但不是第三点:

  
      
  1. 我已多次阅读,首先不应手动设置CMAKE_CXX_FLAGS和类似变量,但我不确定使用其他机制。
  2.   

您想要的命令是set_property。 CMake支持一堆属性 - 不是一切,而是很多 - 这样可以省去编译器特定工作的麻烦。例如:

set_property(TARGET foo PROPERTY CXX_STANDARD 17)

对于某些编译器,将导致--std=c++17,但对于早期的--std=c++1z(在C ++ 17最终确定之前)。或者:

set_property(TARGET foo APPEND PROPERTY COMPILE_DEFINITIONS HELLO WORLD)
对于gcc,clang和MSVC,

会导致-DHELLO -DWORLD,但奇怪的编译器可能会使用其他开关。

  

是否依旧没有像append这样的命令可以让我用set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo")替换append(CMAKE_CXX_FLAGS "Foo")?

set_property可以在设置模式或附加模式下使用(参见上面的示例)。

我不能说这是否比add_compile_optionstarget_compile_features更合适。

答案 2 :(得分:2)

不要这样做!

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

继续阅读以了解为什么


<块引用>

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

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

<块引用>

我目前正在做的事情是这样的:

if(MSVC)
    # ...
else()
    # ...
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        # ...
    else()
        # ...
    endif()
endif()

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

这是这种结构的问题:

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

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

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

使用预设,就像编写 CMakePresets.json 文件一样简单。有一些extensive examples in the documentation。然后你让你的用户告诉你他们想要使用哪组标志:

# 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

等等。

<块引用>

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

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

<块引用>
  • b) 是否有类似公认的现代最佳实践来设置此类项目?

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

答案 3 :(得分:1)

另一种方法是使用.rsp文件。

set(rsp_file "${CMAKE_CURRENT_BINARY_DIR}/my.rsp")
configure_file(my.rsp.in ${rsp_file} @ONLY)
target_compile_options(mytarget PUBLIC "@${rsp_file}")

可能会使包含多个和深奥的选项更容易管理。

答案 4 :(得分:1)

您可以使用target_compile_options()来“追加”编译选项。