Explorar el Código

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.
Raul Tambre hace 4 años
padre
commit
4a0485be7f

+ 5 - 6
Help/manual/cmake-compile-features.7.rst

@@ -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
 =========================
 

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -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 - 0
Help/policy/CMP0128.rst

@@ -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

+ 10 - 0
Help/release/dev/compile-features-standard-logic-rework.rst

@@ -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`.

+ 2 - 0
Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst

@@ -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

+ 1 - 0
Modules/Compiler/IAR-C.cmake

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

+ 1 - 1
Modules/Compiler/IAR-CXX.cmake

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

+ 4 - 1
Source/cmPolicies.h

@@ -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)                                         \

+ 72 - 15
Source/cmStandardLevelResolver.cxx

@@ -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 (cmp0128 == cmPolicies::NEW) {
+      ext = defaultExt;
+    }
+
     if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
-      if (cmIsOff(*extPropValue)) {
-        ext = false;
-      }
+      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 =

+ 8 - 0
Tests/RunCMake/CompileFeatures/CMP0128WarnMatch-stderr.txt

@@ -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\.

+ 7 - 0
Tests/RunCMake/CompileFeatures/CMP0128WarnMatch.cmake

@@ -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@")

+ 8 - 0
Tests/RunCMake/CompileFeatures/CMP0128WarnUnset-stderr.txt

@@ -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\.

+ 6 - 0
Tests/RunCMake/CompileFeatures/CMP0128WarnUnset.cmake

@@ -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@")

+ 8 - 0
Tests/RunCMake/CompileFeatures/NoUnnecessaryFlag-build-check.cmake

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

+ 9 - 0
Tests/RunCMake/CompileFeatures/NoUnnecessaryFlag.cmake

@@ -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@")

+ 116 - 0
Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake

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

+ 12 - 0
Tests/RunCMake/CompileFeatures/UnsetStandard-build-check.cmake

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

+ 8 - 0
Tests/RunCMake/CompileFeatures/UnsetStandard.cmake

@@ -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@")

+ 18 - 0
Tests/RunCMake/CompileFeatures/compiler_introspection.cmake

@@ -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})

+ 1 - 1
Tests/RunCMake/CompileFeatures/empty.c

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