Современный способ установки флагов компилятора в кроссплатформенном проекте 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
,
Так что мои вопросы
- а) Есть ли лучший способ написать сценарий cmake, который решает мои "болевые точки"? решение вопросов, упомянутых выше?
- б) Есть ли что-то вроде общепринятой современной практики создания таких проектов?
Большинство ссылок, которые я мог найти в Интернете, либо устарели, либо показывают только тривиальные примеры. В настоящее время я использую cmake3.8, но если это что-то меняет, меня еще больше интересует ответ для более свежих версий.
5 ответов
Ваш подход - как прокомментировал @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. прокомментировал - "используйте выражения грязного генератора ".
Есть несколько недостатков выражений генератора:
- Очень полезно
$<IF:...,...,...>
выражение доступно только в версии CMake>= 3.8 - Вы должны написать это в одну строку. Чтобы избежать этого, я использовал
string(APPEND ...)
, который вы также можете использовать для "оптимизации" вашегоset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ...
звонки. - Это трудно читать и понимать. Например, точка с запятой необходима, чтобы сделать ее списком параметров компиляции (в противном случае 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-файлов "с одной конфигурацией" или использовать IDE "с несколькими конфигурациями" в качестве Visual Studio.
Вот ссылки на примеры:
- Всегда ли CMake генерирует конфигурации для всех возможных конфигураций проекта?
- Как мне сказать CMake использовать Clang в Windows?
- Как добавить компиляцию Linux в Cmake Project в Visual Studio
Итак, я проверил следующее с Open Folder
Поддержка Visual Studio 2017 CMake для объединения в этом примере компиляторов cl, clang и mingw:
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
посредник (удаление кавычек).
Не делай этого!
Особенно в CMake 3.19+, где являются опцией. Поместите дополнительные настройки, такие как предупреждающие флажки, в предустановки и поместите только жесткие требования к сборке в CMakeLists.txt.
Читайте объяснение почему .
Я хочу написать файл cmake, который устанавливает различные параметры компилятора для clang ++, g ++ и MSVC в сборках отладки и выпуска.
Вот в чем дело: вы не хотите писать CMakeLists.txt, который устанавливает разные параметры, вы просто хотите иметь удобное место для хранения ваших флагов. Вот тут-то и пригодятся файлы пресетов и наборов инструментов (подробнее ниже).
То, что я делаю сейчас, выглядит примерно так:
if(MSVC) # ... else() # ... if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # ... else() # ... endif() endif()
[...] Я читал несколько раз, что не следует вручную устанавливать CMAKE_CXX_FLAGS и подобные переменные в первую очередь, но я не уверен, какой еще механизм использовать.
Вот проблемы с этой структурой:
- Поставщиков компиляторов слишком много . Да, есть MSVC, Clang и GCC, но есть также компилятор Intel, компилятор PGI и так далее.
- Слишком много вариантов компилятора . Есть не только Clang, но также ClangCL и компилятор Clang CUDA. Компилятор Intel также может переключаться между режимами совместимости с MSVC и GCC.
- Слишком много версий компилятора . Значения предупреждающих флажков меняются в зависимости от версии. От одной версии к другой данное предупреждение может быть более или менее чувствительным, особенно более сложные, которые выполняют анализ потока данных. При включении предупреждений как ошибок это приводит к неработающим сборкам для ваших пользователей.
Вы не хотите заниматься поддержкой таблиц совместимости флагов для своей сборки. К счастью, есть простое решение: не надо.
Сохраните намеченные флаги в предустановке (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
и так далее.
Самое главное: как я это делаю здесь, мне нужен отдельный каталог сборки для каждого компилятора и конфигурации. В идеале я хотел бы преобразовать это в наличие нескольких целей в одном каталоге, чтобы я мог, например, позвонить
.
Это странное требование, потому что каждый из них в любом случае должен полностью все построить. Честно говоря, я бы просто рекомендовал настроить Makefile-оболочку для поддержки этого рабочего процесса, потому что (а) CMake не поддерживает его изначально и (б) нет никаких реальных преимуществ для расширения CMake для этого.
- б) Есть ли что-то вроде общепринятой современной передовой практики создания таких проектов?
Я не знаю насчет «принятого», но я абсолютно уверен, что разделение требований жесткой сборки от идеальных настроек сборки между CMakeLists.txt и пресетами (или файлами инструментальной цепочки) соответственно решает (или обходит) многие из этих видов вопросы. Конечным результатом являются более надежные сборки, с которыми легче работать.
Обращаясь к первым двум пунктам, но не к третьему:
- Я прочитал несколько раз, что не следует вручную устанавливать CMAKE_CXX_FLAGS и подобные переменные в первую очередь, но я не уверен, какой другой механизм использовать.
Команда, которую вы хотите 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)
приведет это -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
, хоть.
Вы можете использовать target_compile_options() для "добавления" параметров компиляции.
Другой способ - использовать файлы.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}")
что может упростить управление множественными и эзотерическими вариантами.