Modern way to set compiler flags in cross-platform cmake project

前端 未结 4 1430
再見小時候
再見小時候 2020-11-28 07:05

I want to write a cmake file that sets different compiler options for clang++, g++ and MSVC in debug and release builds. What I\'m doing currently looks something like this:

相关标签:
4条回答
  • 2020-11-28 07:31

    You can use target_compile_options() to "append" compile options.

    0 讨论(0)
  • 2020-11-28 07:35

    Your approach would - as @Tsyvarev has commented - be absolutely fine, just since you've asked for the "new" approach in CMake here is what your code would translate to:

    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)
    

    You take add_compile_options() and - as @Al.G. has commented - "use the dirty generator expressions".

    There are some downsides of generator expressions:

    1. The very helpful $<IF:...,...,...> expression is only available in CMake version >= 3.8
    2. You have to write it in a single line. To avoid it I used the string(APPEND ...), which you can also use to "optimize" your set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ... calls.
    3. It's difficult to read and understand. E.g. the semicolons are needed to make it a list of compile options (otherwise CMake will quote it).

    So better use a more readable and backward compatible approach with 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()
    

    And yes, you don't explicitly specify the C++ standard anymore, you just name the C++ feature your code/target does depend on with target_compile_features() calls.

    For this example I've chosen cxx_lambda_init_captures which would for e.g. an older GCC compiler give the following error (as an example what happens if a compiler does not support this feature):

    The compiler feature "cxx_lambda_init_captures" is not known to CXX compiler
    
    "GNU"
    
    version 4.8.4.
    

    And you need to write a wrapper script to build multiple configurations with a "single configuration" makefile generator or use a "multi configuration" IDE as Visual Studio.

    Here are the references to examples:

    • Does CMake always generate configurations for all possible project configurations?
    • How do I tell CMake to use Clang on Windows?
    • How to Add Linux Compilation to Cmake Project in Visual Studio

    So I've tested the following with the Open Folder Visual Studio 2017 CMake support to combine in this example the cl, clang and mingw compilers:

    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
    

    So you can use any CMake generator from within Visual Studio 2017, there is some unhealthy quoting going on (as for September 2017, maybe fixed later) that requires that mingw32-make.cmd intermediator (removing the quotes).

    0 讨论(0)
  • 2020-11-28 07:35

    Another way is to use .rsp files.

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

    which might make the inclusion of multiple and esoteric options easier to manage.

    0 讨论(0)
  • 2020-11-28 07:41

    Addressing the first two points, but not the third:

    1. I've read multiple times, that one should not manually set CMAKE_CXX_FLAGS and similar variables in the first place, but im not sure what other mechanism to use.

    The command you want is set_property. CMake supports a bunch of properties - not everything, but lots - in a way which saves you the trouble of doing compiler-specific work. For example:

    set_property(TARGET foo PROPERTY CXX_STANDARD 17)
    

    which for some compilers will result in --std=c++17 but for earlier ones with --std=c++1z (before C++17 was finalized). or:

    set_property(TARGET foo APPEND PROPERTY COMPILE_DEFINITIONS HELLO WORLD)
    

    will result it -DHELLO -DWORLD for gcc, clang and MSVC but for weird compilers might use other switches.

    Is there relly no command like append that would allow me to replace set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo") with append(CMAKE_CXX_FLAGS "Foo")?

    set_property can be used either in set mode, or in append mode (see above examples).

    I can't say whether this is preferable to add_compile_options or target_compile_features, though.

    0 讨论(0)
提交回复
热议问题