-
- Downloads
[CMake] Rewrite C++ standard detection test
This patch is a complete rewrite of the existing C++ standard version test that tries the various -std=c++xy flags and tries to figure out which version of the standard to enable. While my cleaned-up version of the old implementation generally works, it has a few problems: - it is not really extensible to newer standards (that will lead to the same kind of mess as before). - More importantly, there is no way to say: I want C++11, but not C++14 (or, in the future, I want C++14, but not C++17). Being able to do so is important for testing and compatibility reason. So this patch mostly starts from scratch: There is a list of supported C++ standards (currently 17,14,11) and corresponding lists of compiler tests and compiler flags to switch the compiler to the respective standards (there might be different values for the switch, e.g. some compilers accept -std=c++1y, but not -std=c++14, and recent versions of GCC reject -std=c++1x). The code then just iterates through these versions and flags until it finds one that works. If that flag / version combo also passes the compile test, the maximum supported standard version is recorded in the variable CXX_MAX_SUPPORTED_STANDARD and the test stops. If the test fails to find a working combination, it issues a warning to the user and only records support for C++03. The test can be influenced by two CMake variables: - DISABLE_CXX_VERSION_CHECK already existed in the old version of the test. It completely disables all testing and requires the user to manually set CXX_MAX_SUPPORTED_STANDARD to the correct value. Moreover, any required compiler command line switches have to be manually added to CMAKE_CXX_FLAGS. - CXX_MAX_STANDARD defines the maximum version of the standard that the build system will try to enable. With this new switch, it becomes possible to limit the compiler to an older version of the standard than what it could theoretically support. For now, this defaults to C++14.
parent
79bd9732
No related branches found
No related tags found
... | ... | @@ -30,89 +30,141 @@ include(CMakePushCheckState) |
include(CheckCXXCompilerFlag) | ||
include(CheckCXXSourceCompiles) | ||
function(dune_check_cxx_14 COMPILER_SUPPORT_VAR) | ||
# C++ standard versions that this test knows about | ||
set(CXX_VERSIONS 17 14 11) | ||
check_cxx_source_compiles(" | ||
#include <memory> | ||
int main() { | ||
// lambdas with auto parameters are C++14 - so this checks the compiler | ||
auto l = [](auto x) { return x; }; | ||
// std::make_unique() is a C++14 library feature - this checks whether the | ||
// compiler uses a C++14 compliant library to work around problems like | ||
// self-installed clang with an older, C++11 only system compiler | ||
auto v = std::make_unique<int>(l(0)); | ||
return *v; | ||
} | ||
" CHECK_COMPILER_SUPPORTS_CXX14) | ||
set(${COMPILER_SUPPORT_VAR} ${CHECK_COMPILER_SUPPORTS_CXX14} PARENT_SCOPE) | ||
# Compile tests for the different standard revisions; these test both the compiler | ||
# and the associated library to avoid problems like using a C++14 user-installed | ||
# compiler together with a non C++14-compliant stdlib from the system compiler. | ||
endfunction() | ||
# we need to escape semicolons in the tests to be able to stick them into a list | ||
string(REPLACE ";" "\;" cxx_17_test | ||
" | ||
#include <type_traits> | ||
function(dune_check_cxx_11 COMPILER_SUPPORT_VAR) | ||
// nested namespaces are a C++17 compiler feature | ||
namespace A::B { | ||
using T = int; | ||
} | ||
check_cxx_source_compiles(" | ||
#include <memory> | ||
int main() { | ||
// std::void_t is a C++17 library feature | ||
return not std::is_same<void,std::void_t<A::B::T> >{}; | ||
} | ||
") | ||
string(REPLACE ";" "\;" cxx_14_test | ||
" | ||
#include <memory> | ||
int main() { | ||
// lambdas with auto parameters are C++14 - so this checks the compiler | ||
auto l = [](auto x) { return x; };a | ||
// std::make_unique() is a C++14 library feature - this checks whether the | ||
// compiler uses a C++14 compliant library. | ||
auto v = std::make_unique<int>(l(0)); | ||
return *v; | ||
} | ||
") | ||
int main() { | ||
// this checks both the compiler (by using auto) and the library (by using | ||
// std::make_shared() for C++11 compliance at GCC 4.4 level. We have to also | ||
// check the library to make sure the user doesn't have a self-installed clang | ||
// without a sufficiently recent system compiler (which might have a pre-C++11 | ||
// C++ library). | ||
auto v = std::make_shared<int>(0); | ||
return *v; | ||
} | ||
" CHECK_COMPILER_SUPPORTS_CXX11) | ||
set(${COMPILER_SUPPORT_VAR} ${CHECK_COMPILER_SUPPORTS_CXX11} PARENT_SCOPE) | ||
string(REPLACE ";" "\;" cxx_11_test | ||
" | ||
#include <memory> | ||
endfunction() | ||
int main() { | ||
// this checks both the compiler (by using auto) and the library (by using | ||
// std::make_shared() for C++11 compliance at GCC 4.4 level. | ||
auto v = std::make_shared<int>(0); | ||
return *v; | ||
} | ||
") | ||
# build a list out of the pre-escaped tests | ||
set(CXX_VERSIONS_TEST "${cxx_17_test}" "${cxx_14_test}" "${cxx_11_test}") | ||
# these are appended to "-std=c++" and tried in this order | ||
# note the escaped semicolons; that's necessary to create a nested list | ||
set(CXX_VERSIONS_FLAGS "17\;1z" "14\;1y" "11\;1x") | ||
|
||
# by default, we enable C++14 for now, but not C++17 | ||
# The user can override this choice by explicitly setting this variable | ||
set(CXX_MAX_STANDARD 14 CACHE STRING "highest version of the C++ standard to enable") | ||
# test for C++14 flags | ||
if(NOT DISABLE_CXX_VERSION_CHECK) | ||
# try to use compiler flag -std=c++14 | ||
check_cxx_compiler_flag("-std=c++14" CXX_STD_CXX14) | ||
if(CXX_STD_CXX14) | ||
set(CXX_STD_FLAG "-std=c++14") | ||
else() | ||
# try to use compiler flag -std=c++1y for older compilers | ||
check_cxx_compiler_flag("-std=c++1y" CXX_STD_CXX1Y) | ||
if (CXX_STD_CXX1Y) | ||
set(CXX_STD_FLAG "-std=c++1y") | ||
endif() | ||
endif() | ||
if(CXX_STD_CXX14 OR CXX_STD_CXX1Y) | ||
cmake_push_check_state() | ||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${CXX_STD_FLAG}") | ||
dune_check_cxx_14(CXX_LIB_SUPPORTS_CXX14) | ||
cmake_pop_check_state() | ||
else() | ||
set(CXX_LIB_SUPPORTS_CXX14 FALSE) | ||
endif() | ||
if(NOT CXX_LIB_SUPPORTS_CXX14) | ||
# try to use compiler flag -std=c++11 | ||
check_cxx_compiler_flag("-std=c++11" CXX_STD_CXX11) | ||
if(CXX_STD_CXX11) | ||
set(CXX_STD_FLAG "-std=c++11") | ||
else() | ||
set(CXX_STD_FLAG "") | ||
# try to enable all of the C++ standards that we know about, in descending order | ||
if(NOT DISABLE_CXX_VERSION_CHECK) | ||
foreach(version ${CXX_VERSIONS}) | ||
# skip versions that are newer than allowed | ||
if(NOT(version GREATER CXX_MAX_STANDARD)) | ||
list(FIND CXX_VERSIONS ${version} version_index) | ||
list(GET CXX_VERSIONS_FLAGS ${version_index} version_flags) | ||
# First try whether the compiler accepts one of the command line flags for this standard | ||
foreach(flag ${version_flags}) | ||
set(cxx_std_flag_works "cxx_std_flag_${flag}") | ||
check_cxx_compiler_flag("-std=c++${flag}" ${cxx_std_flag_works}) | ||
if(${cxx_std_flag_works}) | ||
set(cxx_std_flag "-std=c++${flag}") | ||
break() | ||
endif() | ||
endforeach() | ||
# and if it did, run the compile test | ||
if(cxx_std_flag) | ||
list(GET CXX_VERSIONS_TEST ${version_index} version_test) | ||
set(test_compiler_support "dune_check_cxx_${version}") | ||
set(test_compiler_output "compiler_supports_cxx${version}") | ||
cmake_push_check_state() | ||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${cxx_std_flag}") | ||
check_cxx_source_compiles("${version_test}" ${test_compiler_output}) | ||
cmake_pop_check_state() | ||
if(${test_compiler_output}) | ||
set(CXX_MAX_SUPPORTED_STANDARD ${version}) | ||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxx_std_flag} ") | ||
break() | ||
else() | ||
# Wipe the variable, as this version of the standard doesn't seem to work | ||
unset(cxx_std_flag) | ||
endif() | ||
endif() | ||
endif() | ||
endforeach() | ||
if(NOT DEFINED CXX_MAX_SUPPORTED_STANDARD) | ||
# Let's just assume every compiler at least claims C++03 compliance by now | ||
message(WARNING "\ | ||
Unable to determine C++ standard support for your compiler, falling back to C++03. \ | ||
If you know that your compiler supports a newer version of the standard, please set the CMake \ | ||
variable DISABLE_CXX_VERSION_CHECK to true and the CMake variable CXX_MAX_SUPPORTED_STANDARD \ | ||
to the highest version of the standard supported by your compiler (e.g. 14). If your compiler \ | ||
needs custom flags to switch to that standard version, you have to manually add them to \ | ||
CMAKE_CXX_FLAGS." | ||
) | ||
set(CXX_MAX_SUPPORTED_STANDARD 3) | ||
endif() | ||
endif() | ||
cmake_push_check_state() | ||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${CXX_STD_FLAG}") | ||
dune_check_cxx_11(CXX_LIB_SUPPORTS_CXX11) | ||
cmake_pop_check_state() | ||
if(CXX_LIB_SUPPORTS_CXX11) | ||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STD_FLAG} ") | ||
else() | ||
# make sure we have at least C++11 | ||
if(CXX_MAX_SUPPORTED_STANDARD LESS 11) | ||
message(FATAL_ERROR "Your compiler does not seem to support C++11. If it does, please add any required flags to your CMAKE_CXX_FLAGS.") | ||
endif() | ||
... | ... |