cmStandardLevelResolver: Avoid unnecessary flags, fix unset level logic

The changes are part of CMP0128.

When the standard level is unset:
* Flags are added if extension mode doesn't match the compiler's default.
  Previously logic only worked if LANG_EXTENSIONS was ON. Fixes #22224.
* The full flag is used. Previously CMAKE_LANG_EXTENSION_COMPILE_OPTION was
  used. This was only supported for IAR.

Otherwise:
* Avoid adding flags if not necessary per the detected compiler defaults.
* Fixed check for when the requested standard is older. It now matches the
  nearby comments.

I reworded the fallback comment as its logic was a bit difficult to wrap my
head around.
This commit is contained in:
Raul Tambre 2021-05-29 18:34:18 +03:00
parent 29e2b85171
commit 4a0485be7f
20 changed files with 376 additions and 25 deletions

View File

@ -118,14 +118,13 @@ as well as any dependents (that may include headers from ``mylib``).
Availability of Compiler Extensions
-----------------------------------
Because the :prop_tgt:`CXX_EXTENSIONS` target property is ``ON`` by default,
CMake uses extended variants of language dialects by default, such as
``-std=gnu++11`` instead of ``-std=c++11``. That target property may be
set to ``OFF`` to use the non-extended variant of the dialect flag. Note
that because most compilers enable extensions by default, this could
expose cross-platform bugs in user code or in the headers of third-party
The :prop_tgt:`<LANG>_EXTENSIONS` target property defaults to the compiler's
efault. Note that because most compilers enable extensions by default, this
may expose cross-platform bugs in user code or in the headers of third-party
dependencies.
:prop_tgt:`<LANG>_EXTENSIONS` used to default to ``ON``. See :policy:`CMP0128`.
Optional Compile Features
=========================

View File

@ -57,6 +57,7 @@ Policies Introduced by CMake 3.22
.. toctree::
:maxdepth: 1
CMP0128: Selection of language standard and extension flags improved. </policy/CMP0128>
CMP0127: cmake_dependent_option() supports full Condition Syntax. </policy/CMP0127>
Policies Introduced by CMake 3.21

78
Help/policy/CMP0128.rst Normal file
View File

@ -0,0 +1,78 @@
CMP0128
-------
.. versionadded:: 3.22
When this policy is set to ``NEW``:
* :prop_tgt:`<LANG>_EXTENSIONS` is initialized to
:variable:`CMAKE_<LANG>_EXTENSIONS_DEFAULT`.
* Extensions are correctly disabled/enabled if :prop_tgt:`<LANG>_STANDARD` is
unset.
* Standard mode-affecting flags aren't added unless necessary to achieve the
specified mode.
The ``OLD`` behavior:
* Initializes :prop_tgt:`<LANG>_EXTENSIONS` to ``ON``.
* Always adds a flag if :prop_tgt:`<LANG>_STANDARD` is set and
:prop_tgt:`<LANG>_STANDARD_REQUIRED` is ``OFF``.
* If :prop_tgt:`<LANG>_STANDARD` is unset:
* Doesn't disable extensions even if :prop_tgt:`<LANG>_EXTENSIONS` is
``OFF``.
* Fails to enable extensions if :prop_tgt:`<LANG>_EXTENSIONS` is ``ON``
except for the ``IAR`` compiler.
Code may need to be updated for the ``NEW`` behavior in the following cases:
* If :prop_tgt:`<LANG>_EXTENSIONS` matches
:variable:`CMAKE_<LANG>_EXTENSIONS_DEFAULT` or is unset and the compiler's
default satisfies :prop_tgt:`<LANG>_STANDARD` but the compiled code requires
the exact standard specified.
Such code should set :prop_tgt:`<LANG>_STANDARD_REQUIRED` to ``ON``.
For example:
.. code-block:: cmake
cmake_minimum_required(VERSION |release|)
project(example C)
add_executable(exe main.c)
set_property(TARGET exe PROPERTY C_STANDARD 99)
If the compiler defaults to C11 then the standard specification for C99 is
satisfied and CMake will pass no flags. ``main.c`` will no longer compile if
it is incompatible with C11.
* If a standard mode flag previously overridden by CMake's and not used during
compiler detection now takes effect due to CMake no longer adding one as the
default detected is appropriate.
Such code should be converted to either:
* Use :prop_tgt:`<LANG>_STANDARD` and :prop_tgt:`<LANG>_EXTENSIONS` instead
of manually adding flags.
* Or ensure the manually-specified flags are used during compiler detection.
If compiler flags affecting the standard mode are used during compiler
detection (for example in :manual:`a toolchain file <cmake-toolchains(7)>`
using :variable:`CMAKE_<LANG>_FLAGS_INIT`) then they will affect the detected
default :variable:`standard <CMAKE_<LANG>_STANDARD_DEFAULT>` and
:variable:`extensions <CMAKE_<LANG>_EXTENSIONS_DEFAULT>`.
Unlike many policies, CMake version |release| does *not* warn when the policy
is not set and simply uses the ``OLD`` behavior. Use the
:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
See documentation of the
:variable:`CMAKE_POLICY_WARNING_CMP0128 <CMAKE_POLICY_WARNING_CMP<NNNN>>`
variable to control the warning.
.. include:: DEPRECATED.txt

View File

@ -0,0 +1,10 @@
compile-features-standard-logic-rework
--------------------------------------
* The :manual:`Compile Features <cmake-compile-features(7)>` functionality now
correctly disables or enables compiler extensions when no standard level is
specified and avoids unnecessarily adding language standard flags if the
requested settings match the compiler's defaults. See :policy:`CMP0128`.
* :prop_tgt:`<LANG>_EXTENSIONS` is initialized to
:variable:`CMAKE_<LANG>_EXTENSIONS_DEFAULT`. See :policy:`CMP0128`.

View File

@ -32,6 +32,8 @@ only for the policies that do not warn by default:
policy :policy:`CMP0116`.
* ``CMAKE_POLICY_WARNING_CMP0126`` controls the warning for
policy :policy:`CMP0126`.
* ``CMAKE_POLICY_WARNING_CMP0128`` controls the warning for
policy :policy:`CMP0128`.
This variable should not be set by a project in CMake code. Project
developers running CMake may set this variable in their cache to

View File

@ -14,6 +14,7 @@ if(NOT DEFINED CMAKE_C_COMPILER_VERSION)
message(FATAL_ERROR "CMAKE_C_COMPILER_VERSION not detected. This should be automatic.")
endif()
# Unused after CMP0128
set(CMAKE_C_EXTENSION_COMPILE_OPTION -e)
if(CMAKE_C_COMPILER_VERSION_INTERNAL VERSION_GREATER 7)

View File

@ -27,7 +27,7 @@ if(NOT CMAKE_IAR_CXX_FLAG)
endif()
set(CMAKE_CXX_STANDARD_COMPILE_OPTION "")
set(CMAKE_CXX_EXTENSION_COMPILE_OPTION -e)
set(CMAKE_CXX_EXTENSION_COMPILE_OPTION -e) # Unused after CMP0128
set(CMAKE_CXX${CMAKE_CXX_STANDARD_COMPUTED_DEFAULT}_STANDARD_COMPILE_OPTION "")
set(CMAKE_CXX${CMAKE_CXX_STANDARD_COMPUTED_DEFAULT}_EXTENSION_COMPILE_OPTION -e)

View File

@ -382,7 +382,10 @@ class cmMakefile;
21, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0127, \
"cmake_dependent_option() supports full Condition Syntax.", 3, 22, \
0, cmPolicies::WARN)
0, cmPolicies::WARN) \
SELECT(POLICY, CMP0128, \
"Selection of language standard and extension flags improved.", 3, \
22, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \

View File

@ -20,6 +20,7 @@
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmTarget.h"
#include "cmValue.h"
@ -83,25 +84,62 @@ struct StandardLevelComputer
return std::string{};
}
cmPolicies::PolicyStatus const cmp0128{ makefile->GetPolicyStatus(
cmPolicies::CMP0128) };
bool const defaultExt{ cmIsOn(*makefile->GetDefinition(
cmStrCat("CMAKE_", this->Language, "_EXTENSIONS_DEFAULT"))) };
bool ext = true;
if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
if (cmIsOff(*extPropValue)) {
ext = false;
}
if (cmp0128 == cmPolicies::NEW) {
ext = defaultExt;
}
if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
ext = cmIsOn(*extPropValue);
}
std::string const type{ ext ? "EXTENSION" : "STANDARD" };
cmValue standardProp = target->GetLanguageStandard(this->Language, config);
if (!standardProp) {
if (ext) {
// No language standard is specified and extensions are not disabled.
// Check if this compiler needs a flag to enable extensions.
return cmStrCat("CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION");
if (cmp0128 == cmPolicies::NEW) {
// Add extension flag if compiler's default doesn't match.
if (ext != defaultExt) {
return cmStrCat("CMAKE_", this->Language, *defaultStd, "_", type,
"_COMPILE_OPTION");
}
} else {
if (cmp0128 == cmPolicies::WARN &&
makefile->PolicyOptionalWarningEnabled(
"CMAKE_POLICY_WARNING_CMP0128") &&
ext != defaultExt) {
const char* state{};
if (ext) {
if (!makefile->GetDefinition(cmStrCat(
"CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION"))) {
state = "enabled";
}
} else {
state = "disabled";
}
if (state) {
makefile->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
"\nFor compatibility with older versions of CMake, "
"compiler extensions won't be ",
state, "."));
}
}
if (ext) {
return cmStrCat("CMAKE_", this->Language,
"_EXTENSION_COMPILE_OPTION");
}
}
return std::string{};
}
std::string const type = ext ? "EXTENSION" : "STANDARD";
if (target->GetLanguageStandardRequired(this->Language)) {
std::string option_flag = cmStrCat(
"CMAKE_", this->Language, *standardProp, "_", type, "_COMPILE_OPTION");
@ -121,6 +159,25 @@ struct StandardLevelComputer
return option_flag;
}
// If the request matches the compiler's defaults we don't need to add
// anything.
if (*standardProp == *defaultStd && ext == defaultExt) {
if (cmp0128 == cmPolicies::NEW) {
return std::string{};
}
if (cmp0128 == cmPolicies::WARN &&
makefile->PolicyOptionalWarningEnabled(
"CMAKE_POLICY_WARNING_CMP0128")) {
makefile->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
"\nFor compatibility with older versions of CMake, "
"unnecessary flags for language standard or compiler "
"extensions may be added."));
}
}
std::string standardStr(*standardProp);
if (this->Language == "CUDA" && standardStr == "98") {
standardStr = "03";
@ -147,17 +204,17 @@ struct StandardLevelComputer
return std::string{};
}
// If the standard requested is older than the compiler's default
// then we need to use a flag to change it.
if (stdIt <= defaultStdIt) {
// If the standard requested is older than the compiler's default or the
// extension mode doesn't match then we need to use a flag.
if (stdIt < defaultStdIt) {
auto offset = std::distance(cm::cbegin(stds), stdIt);
return cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
"_COMPILE_OPTION");
}
// The standard requested is at least as new as the compiler's default,
// and the standard request is not required. Decay to the newest standard
// for which a flag is defined.
// The compiler's default is at least as new as the requested standard,
// and the requested standard is not required. Decay to the newest
// standard for which a flag is defined.
for (; defaultStdIt < stdIt; --stdIt) {
auto offset = std::distance(cm::cbegin(stds), stdIt);
std::string option_flag =

View File

@ -0,0 +1,8 @@
CMake Warning \(dev\) in CMakeLists\.txt:
Policy CMP0128 is not set: Selection of language standard and extension
flags improved\. Run "cmake --help-policy CMP0128" for policy details\. Use
the cmake_policy command to set the policy and suppress this warning\.
For compatibility with older versions of CMake, unnecessary flags for
language standard or compiler extensions may be added.
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@ -0,0 +1,7 @@
enable_language(@lang@)
cmake_policy(SET CMP0128 OLD)
set(CMAKE_POLICY_WARNING_CMP0128 ON)
set(CMAKE_@lang@_EXTENSIONS @extensions_default@)
set(CMAKE_@lang@_STANDARD @standard_default@)
add_library(foo "@RunCMake_SOURCE_DIR@/empty.@ext@")

View File

@ -0,0 +1,8 @@
CMake Warning \(dev\) in CMakeLists\.txt:
Policy CMP0128 is not set: Selection of language standard and extension
flags improved\. Run "cmake --help-policy CMP0128" for policy details\. Use
the cmake_policy command to set the policy and suppress this warning\.
For compatibility with older versions of CMake, compiler extensions won't
be @opposite@\.
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@ -0,0 +1,6 @@
enable_language(@lang@)
cmake_policy(SET CMP0128 OLD)
set(CMAKE_POLICY_WARNING_CMP0128 ON)
set(CMAKE_@lang@_EXTENSIONS @extensions_opposite@)
add_library(foo "@RunCMake_SOURCE_DIR@/empty.@ext@")

View File

@ -0,0 +1,8 @@
foreach(flag @flags@)
string(FIND "${actual_stdout}" "${flag}" position)
if(NOT position EQUAL -1)
set(RunCMake_TEST_FAILED "\"${flag}\" compile flag found.")
break()
endif()
endforeach()

View File

@ -0,0 +1,9 @@
enable_language(@lang@)
# Make sure the compile command is not hidden.
string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_@lang@_COMPILE_OBJECT "${CMAKE_@lang@_COMPILE_OBJECT}")
string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_@lang@_COMPILE_OBJECT "${CMAKE_@lang@_COMPILE_OBJECT}")
set(CMAKE_@lang@_EXTENSIONS @extensions_default@)
set(CMAKE_@lang@_STANDARD @standard_default@)
add_library(foo "@RunCMake_SOURCE_DIR@/empty.@ext@")

View File

@ -34,6 +34,120 @@ elseif (cxx_std_98 IN_LIST CXX_FEATURES AND cxx_std_11 IN_LIST CXX_FEATURES)
unset(RunCMake_TEST_OPTIONS)
endif()
configure_file("${RunCMake_SOURCE_DIR}/CMakeLists.txt" "${RunCMake_BINARY_DIR}/CMakeLists.txt" COPYONLY)
macro(test_build)
set(test ${name}-${lang})
configure_file("${RunCMake_SOURCE_DIR}/${name}.cmake" "${RunCMake_BINARY_DIR}/${test}.cmake" @ONLY)
if(EXISTS "${RunCMake_SOURCE_DIR}/${name}-build-check.cmake")
configure_file("${RunCMake_SOURCE_DIR}/${name}-build-check.cmake" "${RunCMake_BINARY_DIR}/${test}-build-check.cmake" @ONLY)
endif()
if(EXISTS "${RunCMake_SOURCE_DIR}/${name}-stderr.txt")
configure_file("${RunCMake_SOURCE_DIR}/${name}-stderr.txt" "${RunCMake_BINARY_DIR}/${test}-stderr.txt" @ONLY)
endif()
set(RunCMake_SOURCE_DIR "${RunCMake_BINARY_DIR}")
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${test}-build")
run_cmake(${test})
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . ${ARGN})
endmacro()
# Mangle flags such as they're in verbose build output.
macro(mangle_flags variable)
set(result "${${variable}}")
if(RunCMake_GENERATOR MATCHES "Visual Studio" AND MSVC_TOOLSET_VERSION GREATER_EQUAL 141)
string(REPLACE "-" "/" result "${result}")
elseif(RunCMake_GENERATOR STREQUAL "Xcode" AND CMAKE_XCODE_BUILD_SYSTEM GREATER_EQUAL 12)
string(REPLACE "=" [[\\=]] result "${result}")
endif()
string(REPLACE ";" " " result "${result}")
list(APPEND flags "${result}")
endmacro()
function(test_unset_standard)
if(extensions_opposite)
set(flag_ext "_EXT")
endif()
set(flag "${${lang}${${lang}_STANDARD_DEFAULT}${flag_ext}_FLAG}")
if(NOT flag)
return()
endif()
mangle_flags(flag)
set(name UnsetStandard)
set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0128=NEW)
test_build(--verbose)
endfunction()
function(test_no_unnecessary_flag)
set(standard_flag "${${lang}${${lang}_STANDARD_DEFAULT}_FLAG}")
set(extension_flag "${${lang}${${lang}_STANDARD_DEFAULT}_EXT_FLAG}")
if(NOT standard_flag AND NOT extension_flag)
return()
endif()
mangle_flags(standard_flag)
mangle_flags(extension_flag)
set(name NoUnnecessaryFlag)
set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0128=NEW)
test_build(--verbose)
endfunction()
function(test_cmp0128_warn_match)
set(name CMP0128WarnMatch)
test_build()
endfunction()
function(test_cmp0128_warn_unset)
# For compilers that had CMAKE_<LANG>_EXTENSION_COMPILE_OPTION (only IAR)
# there is no behavioural change and thus no warning.
if(NOT "${${lang}_EXT_FLAG}" STREQUAL "")
return()
endif()
if(extensions_opposite)
set(opposite "enabled")
else()
set(opposite "disabled")
endif()
set(name CMP0128WarnUnset)
test_build()
endfunction()
function(test_lang lang ext)
if(CMake_NO_${lang}_STANDARD)
return()
endif()
set(extensions_default "${${lang}_EXTENSIONS_DEFAULT}")
set(standard_default "${${lang}_STANDARD_DEFAULT}")
if(extensions_default)
set(extensions_opposite OFF)
else()
set(extensions_opposite ON)
endif()
test_unset_standard()
test_no_unnecessary_flag()
test_cmp0128_warn_match()
test_cmp0128_warn_unset()
endfunction()
if(C_STANDARD_DEFAULT)
test_lang(C c)
endif()
if(CXX_STANDARD_DEFAULT)
run_cmake(NotAStandard)
@ -47,4 +161,6 @@ if(CXX_STANDARD_DEFAULT)
run_cmake(RequireCXX${standard}ExtVariable)
endif()
endforeach()
test_lang(CXX cpp)
endif()

View File

@ -0,0 +1,12 @@
foreach(flag @flags@)
string(FIND "${actual_stdout}" "${flag}" position)
if(NOT position EQUAL -1)
set(found TRUE)
break()
endif()
endforeach()
if(NOT found)
set(RunCMake_TEST_FAILED "No compile flags from \"@flags@\" found for CMAKE_@lang@_EXTENSIONS=@extensions_opposite@.")
endif()

View File

@ -0,0 +1,8 @@
enable_language(@lang@)
# Make sure the compile command is not hidden.
string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_@lang@_COMPILE_OBJECT "${CMAKE_@lang@_COMPILE_OBJECT}")
string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_@lang@_COMPILE_OBJECT "${CMAKE_@lang@_COMPILE_OBJECT}")
set(CMAKE_@lang@_EXTENSIONS @extensions_opposite@)
add_library(foo "@RunCMake_SOURCE_DIR@/empty.@ext@")

View File

@ -1,10 +1,28 @@
enable_language(C CXX)
set(info "")
if(MSVC_TOOLSET_VERSION)
string(APPEND info "
set(MSVC_TOOLSET_VERSION ${MSVC_TOOLSET_VERSION})
")
endif()
if(CMAKE_XCODE_BUILD_SYSTEM)
string(APPEND info "
set(CMAKE_XCODE_BUILD_SYSTEM ${CMAKE_XCODE_BUILD_SYSTEM})
")
endif()
macro(info lang)
string(APPEND info "\
set(${lang}_STANDARD_DEFAULT ${CMAKE_${lang}_STANDARD_DEFAULT})
set(${lang}_EXTENSIONS_DEFAULT ${CMAKE_${lang}_EXTENSIONS_DEFAULT})
set(${lang}_FEATURES ${CMAKE_${lang}_COMPILE_FEATURES})
set(${lang}_EXT_FLAG ${CMAKE_${lang}_EXTENSION_COMPILE_OPTION})
")
foreach(standard ${ARGN})

View File

@ -1,7 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
int empty(void)
{
return 0;
}