Современный способ установки флагов компилятора в кросс-платформенном проекте 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()

Но у меня есть пара проблем с этим:

  • Сначала тривиальным: не существует ли команды вроде appen, которая позволила бы мне заменить set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo") на append(CMAKE_CXX_FLAGS "Foo")?
  • Я читал несколько раз, что не следует вручную устанавливать CMAKE_CXX_FLAGS и подобные переменные в первую очередь, но я не уверен, какой другой механизм использовать.
  • Самое главное: способ, которым я это делаю здесь, мне нужен отдельный каталог сборки для каждого компилятора и конфигурации. В идеале я хотел бы преобразовать это в несколько целей в одном каталоге, чтобы я мог, например. вызов make foo_debug_clang.

Итак, мои вопросы

  • a) Есть ли лучший способ написать cmake script, который решает мои "болевые точки"? решение проблем, упомянутых выше?
  • b) Есть ли что-то вроде принятой, современной передовой практики создания таких проектов?

Большинство ссылок, которые я мог найти в Интернете, либо устарели, либо показывают только тривиальные примеры. В настоящее время я использую cmake3.8, но если это имеет значение, меня больше интересует ответ для более поздних версий.

Ответ 1

Ваш подход был бы, как прокомментировал @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. прокомментировал - "используйте грязные выражения генератора".

Есть несколько недостатков выражений генератора:

  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()

И да, вы больше не указываете явно стандарт 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.

Кроме того, вам нужно написать скрипт-обертку для создания нескольких конфигураций с помощью генератора make файлов "с одной конфигурацией" или использовать "среду с несколькими конфигурациями" в качестве Visual Studio.

Вот ссылки на примеры:

Итак, я протестировал следующее с поддержкой Open Folder Visual Studio 2017 CMake, чтобы объединить в этом примере компиляторы , и :

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

Таким образом, вы можете использовать любой генератор CMake из Visual Studio 2017, происходит нездоровое цитирование (как в сентябре 2017 года, может быть исправлено позже), для которого требуется mingw32-make.cmd посредник (удаление кавычек).

Ответ 2

Адресация первых двух точек, но не третья:

  1. Я читал несколько раз, что вначале не нужно вручную устанавливать CMAKE_CXX_FLAGS и подобные переменные, но я не уверен, какой другой механизм использовать.

Команда, которую вы хотите, - set_property. CMake поддерживает множество свойств - не все, а множество - таким образом, что избавляет вас от необходимости выполнять работу, связанную с компилятором. Например:

set_property(TARGET foo PROPERTY CXX_STANDARD 17)

который для некоторых компиляторов приведет к --std=c++17, но для более ранних с --std=c++1z (до завершения С++ 17). или:

set_property(TARGET foo APPEND PROPERTY COMPILE_DEFINITIONS HELLO WORLD)

приведет к его -DHELLO -DWORLD для gcc, clang и MSVC, но для странных компиляторов могут использовать другие ключи.

Не существует ли команды вроде append, которая позволила бы мне заменить set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo") на append(CMAKE_CXX_FLAGS "Foo")?

set_property можно использовать либо в режиме установки, либо в режиме добавления (см. выше примеры).

Я не могу сказать, предпочтительнее ли это для add_compile_options или target_compile_features.

Ответ 3

Другим способом является использование файлов .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

Вы можете использовать target_compile_options() для "добавления" параметров компиляции.