在跨平台 cmake 项目中设置编译器标志的现代方法
我想编写一个 cmake 文件,在调试和发布版本中为 clang++、g++ 和 MSVC 设置不同的编译器选项.我目前正在做的事情看起来像这样:
if(MSVC)设置(CMAKE_CXX_FLAGS${CMAKE_CXX_FLAGS}/std:c++latest/W4")# 默认调试标志没问题设置(CMAKE_CXX_FLAGS_RELEASE{CMAKE_CXX_FLAGS_RELEASE}/O2")别的()设置(CMAKE_CXX_FLAGS${CMAKE_CXX_FLAGS} -std=c++1z -Wall -Wextra -Werror")设置(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} 一些其他标志")设置(CMAKE_CXX_FLAGS_RELEASE${CMAKE_CXX_FLAGS_RELEASE} -O3")if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")设置(CMAKE_CXX_FLAGS${CMAKE_CXX_FLAGS} -stdlib=libc++")别的()# 目前对 gcc 没有什么特别的万一()万一()
但是我有几个问题:
- 首先是微不足道的:是否真的没有像 appen 这样的命令可以让我将
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo")
替换为append(CMAKE_CXX_FLAGS "Foo")代码>?
- 我已经多次阅读,一开始就不应该手动设置
CMAKE_CXX_FLAGS
和类似的变量,但我不确定要使用什么其他机制. - 最重要的是:我在这里这样做的方式,我需要为每个编译器和配置单独的构建目录理想情况下,我想将其转换为同一目录中的多个目标,以便我可以例如调用
make foo_debug_clang
.
所以我的问题是
- a) 有没有更好的方法来编写解决我的痛点"的 cmake 脚本?以上几点的解决方案?
- b) 是否有类似公认的现代最佳实践来设置此类项目?
我在互联网上找到的大多数参考资料要么过时,要么只展示了一些琐碎的例子.我目前使用 cmake3.8,但如果这有什么不同,我对更新版本的答案更感兴趣.
解决方案您的方法 - 正如@Tsyvarev 所评论的那样 - 绝对没问题,因为您在 CMake 中要求新"方法,这就是您的代码将转化为:
cmake_minimum_required(VERSION 3.8)项目(你好世界)细绳(APPEND _opts"$,""/W4;$<$:/O2>,"-Wall;-Wextra;-Werror;"$<$:-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)
<小时>
您使用
CMakeSettings.json
<代码>{//有关此文件的更多信息,请参阅 https://go.microsoft.com//fwlink//?linkid=834763.配置":[{"name": "x86-调试","generator": "Visual Studio 15 2017","configurationType": "调试","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}","buildCommandArgs": "-m -v:minimal",},{"name": "x86-Release","generator": "Visual Studio 15 2017","configurationType": "发布","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}","buildCommandArgs": "-m -v:minimal",},{"name": "Clang-调试","generator": "Visual Studio 15 2017","configurationType": "调试","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}","cmakeCommandArgs": "-T"LLVM-vs2014"","buildCommandArgs": "-m -v:minimal",},{"name": "Clang-Release","generator": "Visual Studio 15 2017","configurationType": "发布","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}","cmakeCommandArgs": "-T"LLVM-vs2014"","buildCommandArgs": "-m -v:minimal",},{"name": "GNU 调试","generator": "MinGW Makefiles","configurationType": "调试","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}",变量":[{"name": "CMAKE_MAKE_PROGRAM","value": "${projectDir}\mingw32-make.cmd"}]},{"name": "GNU 版本","generator": "Unix Makefiles","configurationType": "发布","buildRoot": "${env.LOCALAPPDATA}\CMakeBuild\${workspaceHash}\build\${name}",变量":[{"name": "CMAKE_MAKE_PROGRAM","value": "${projectDir}\mingw32-make.cmd"}]}]}
mingw32-make.cmd
@echo offmingw32-make.exe %~1 %~2 %~3 %~4
因此,您可以在 Visual Studio 2017 中使用任何 CMake 生成器,但有一些不健康的引用(如 2017 年 9 月,可能稍后修复)需要 mingw32-make.cmd
中介器(删除引号).
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:
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()
But I have a couple of problems with this:
- First the trivial: Is there relly no command like appen that would allow me to replace
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo")
withappend(CMAKE_CXX_FLAGS "Foo")
? - 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. - Most importantly: The way I do it here, I need a separate build directory for each compiler and configuration Ideally I'd like to transform that into havin multiple targets in the same directory so I can e.g. call
make foo_debug_clang
.
So my questions are
- a) Is there a better way to write th cmake script that solves my "pain points"? solution to the points mentioned above?
- b) Is there something like an accepted, modern best practice of how to set up such projects?
Most references I could find on the internet are either out of date or show only trivial examples. I currently using cmake3.8, but if that makes any difference, I'm even more interested in the answer for more recent versions.
解决方案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:
- The very helpful
$<IF:...,...,...>
expression is only available in CMake version >= 3.8 - You have to write it in a single line. To avoid it I used the
string(APPEND ...)
, which you can also use to "optimize" yourset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ...
calls. - 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).
相关文章