Browse Source

try_compile: Add policy CMP0067 to honor language standards

Projects use `try_compile` to check if they will be able to compile some
particular source code.  When a language standard variable like
`CMAKE_CXX_STANDARD` is set, then the project intends to compile source
code using a compiler mode for that standard.  Therefore it makes sense
for `try_compile` to use that standard in the test project too.

Unfortunately this was not done when support for the
`CMAKE_CXX_STANDARD` variable was first implemented.  Add a policy to
introduce the improved behavior in a compatible way.

Closes: #16456
Brad King 9 years ago
parent
commit
f72ba42b7c

+ 14 - 0
Help/command/try_compile.rst

@@ -135,3 +135,17 @@ the type of target used for the source file signature.
 Set the :variable:`CMAKE_TRY_COMPILE_PLATFORM_VARIABLES` variable to specify
 variables that must be propagated into the test project.  This variable is
 meant for use only in toolchain files.
+
+If :policy:`CMP0067` is set to ``NEW``, or any of the ``<LANG>_STANDARD``,
+``<LANG>_STANDARD_REQUIRED``, or ``<LANG>_EXTENSIONS`` options are used,
+then the language standard variables are honored:
+
+* :variable:`CMAKE_C_STANDARD`
+* :variable:`CMAKE_C_STANDARD_REQUIRED`
+* :variable:`CMAKE_C_EXTENSIONS`
+* :variable:`CMAKE_CXX_STANDARD`
+* :variable:`CMAKE_CXX_STANDARD_REQUIRED`
+* :variable:`CMAKE_CXX_EXTENSIONS`
+
+Their values are used to set the corresponding target properties in
+the generated project (unless overridden by an explicit option).

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

@@ -51,6 +51,14 @@ The :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` variable may also be used
 to determine whether to report an error on use of deprecated macros or
 functions.
 
+Policies Introduced by CMake 3.8
+================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0067: Honor language standard in try_compile() source-file signature. </policy/CMP0067>
+
 Policies Introduced by CMake 3.7
 ================================
 

+ 34 - 0
Help/policy/CMP0067.rst

@@ -0,0 +1,34 @@
+CMP0067
+-------
+
+Honor language standard in :command:`try_compile` source-file signature.
+
+The :command:`try_compile` source file signature is intended to allow
+callers to check whether they will be able to compile a given source file
+with the current toolchain.  In order to match compiler behavior, any
+language standard mode should match.  However, CMake 3.7 and below did not
+do this.  CMake 3.8 and above prefer to honor the language standard settings
+for ``C`` and ``CXX`` (C++) using the values of the variables:
+
+* :variable:`CMAKE_C_STANDARD`
+* :variable:`CMAKE_C_STANDARD_REQUIRED`
+* :variable:`CMAKE_C_EXTENSIONS`
+* :variable:`CMAKE_CXX_STANDARD`
+* :variable:`CMAKE_CXX_STANDARD_REQUIRED`
+* :variable:`CMAKE_CXX_EXTENSIONS`
+
+This policy provides compatibility for projects that do not expect
+the language standard settings to be used automatically.
+
+The ``OLD`` behavior of this policy is to ignore language standard
+setting variables when generating the ``try_compile`` test project.
+The ``NEW`` behavior of this policy is to honor language standard
+setting variables.
+
+This policy was introduced in CMake version 3.8.  Unlike most policies,
+CMake version |release| does *not* warn by default when this policy
+is not set and simply uses OLD behavior.  See documentation of the
+:variable:`CMAKE_POLICY_WARNING_CMP0067 <CMAKE_POLICY_WARNING_CMP<NNNN>>`
+variable to control the warning.
+
+.. include:: DEPRECATED.txt

+ 4 - 0
Help/release/dev/try_compile-lang-std.rst

@@ -3,3 +3,7 @@ try_compile-lang-std
 
 * The :command:`try_compile` command source file signature gained new options
   to specify the language standard to use in the generated test project.
+
+* The :command:`try_compile` command source file signature now honors
+  language standard variables like :variable:`CMAKE_CXX_STANDARD`.
+  See policy :policy:`CMP0067`.

+ 2 - 0
Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst

@@ -17,6 +17,8 @@ warn by default:
   policy :policy:`CMP0065`.
 * ``CMAKE_POLICY_WARNING_CMP0066`` controls the warning for
   policy :policy:`CMP0066`.
+* ``CMAKE_POLICY_WARNING_CMP0067`` controls the warning for
+  policy :policy:`CMP0067`.
 
 This variable should not be set by a project in CMake code.  Project
 developers running CMake may set this variable in their cache to

+ 79 - 0
Source/cmCoreTryCompile.cxx

@@ -54,6 +54,17 @@ static void writeProperty(FILE* fout, std::string const& targetName,
           cmOutputConverter::EscapeForCMake(value).c_str());
 }
 
+std::string cmCoreTryCompile::LookupStdVar(std::string const& var,
+                                           bool warnCMP0067)
+{
+  std::string value = this->Makefile->GetSafeDefinition(var);
+  if (warnCMP0067 && !value.empty()) {
+    value.clear();
+    this->WarnCMP0067.push_back(var);
+  }
+  return value;
+}
+
 int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
                                      bool isTryRun)
 {
@@ -620,6 +631,74 @@ int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
     bool const testC = testLangs.find("C") != testLangs.end();
     bool const testCxx = testLangs.find("CXX") != testLangs.end();
 
+    bool warnCMP0067 = false;
+    bool honorStandard = true;
+
+    if (!didCStandard && !didCxxStandard && !didCStandardRequired &&
+        !didCxxStandardRequired && !didCExtensions && !didCxxExtensions) {
+      switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0067)) {
+        case cmPolicies::WARN:
+          warnCMP0067 = this->Makefile->PolicyOptionalWarningEnabled(
+            "CMAKE_POLICY_WARNING_CMP0067");
+        case cmPolicies::OLD:
+          // OLD behavior is to not honor the language standard variables.
+          honorStandard = false;
+          break;
+        case cmPolicies::REQUIRED_IF_USED:
+        case cmPolicies::REQUIRED_ALWAYS:
+          this->Makefile->IssueMessage(
+            cmake::FATAL_ERROR,
+            cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0067));
+        case cmPolicies::NEW:
+          // NEW behavior is to honor the language standard variables.
+          // We already initialized honorStandard to true.
+          break;
+      }
+    }
+
+    if (honorStandard || warnCMP0067) {
+      if (testC) {
+        if (!didCStandard) {
+          cStandard = this->LookupStdVar("CMAKE_C_STANDARD", warnCMP0067);
+        }
+        if (!didCStandardRequired) {
+          cStandardRequired =
+            this->LookupStdVar("CMAKE_C_STANDARD_REQUIRED", warnCMP0067);
+        }
+        if (!didCExtensions) {
+          cExtensions = this->LookupStdVar("CMAKE_C_EXTENSIONS", warnCMP0067);
+        }
+      }
+      if (testCxx) {
+        if (!didCxxStandard) {
+          cxxStandard = this->LookupStdVar("CMAKE_CXX_STANDARD", warnCMP0067);
+        }
+        if (!didCxxStandardRequired) {
+          cxxStandardRequired =
+            this->LookupStdVar("CMAKE_CXX_STANDARD_REQUIRED", warnCMP0067);
+        }
+        if (!didCxxExtensions) {
+          cxxExtensions =
+            this->LookupStdVar("CMAKE_CXX_EXTENSIONS", warnCMP0067);
+        }
+      }
+    }
+
+    if (!this->WarnCMP0067.empty()) {
+      std::ostringstream w;
+      /* clang-format off */
+      w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0067) << "\n"
+        "For compatibility with older versions of CMake, try_compile "
+        "is not honoring language standard variables in the test project:\n"
+        ;
+      /* clang-format on */
+      for (std::vector<std::string>::iterator vi = this->WarnCMP0067.begin();
+           vi != this->WarnCMP0067.end(); ++vi) {
+        w << "  " << *vi << "\n";
+      }
+      this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, w.str());
+    }
+
     if (testC) {
       if (!cStandard.empty()) {
         writeProperty(fout, targetName, "C_STANDARD", cStandard);

+ 4 - 0
Source/cmCoreTryCompile.h

@@ -47,6 +47,10 @@ protected:
   std::string OutputFile;
   std::string FindErrorMessage;
   bool SrcFileSignature;
+
+private:
+  std::vector<std::string> WarnCMP0067;
+  std::string LookupStdVar(std::string const& var, bool warnCMP0067);
 };
 
 #endif

+ 4 - 1
Source/cmPolicies.h

@@ -197,7 +197,10 @@ class cmMakefile;
          3, 4, 0, cmPolicies::WARN)                                           \
   SELECT(POLICY, CMP0066,                                                     \
          "Honor per-config flags in try_compile() source-file signature.", 3, \
-         7, 0, cmPolicies::WARN)
+         7, 0, cmPolicies::WARN)                                              \
+  SELECT(POLICY, CMP0067,                                                     \
+         "Honor language standard in try_compile() source-file signature.",   \
+         3, 8, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 25 - 0
Tests/RunCMake/try_compile/CMP0067-stderr.txt

@@ -0,0 +1,25 @@
+before try_compile with CMP0067 WARN-enabled but no variables
+after try_compile with CMP0067 WARN-enabled but no variables
+before try_compile with CMP0067 WARN-default
+after try_compile with CMP0067 WARN-default
+before try_compile with CMP0067 WARN-enabled
+CMake Warning \(dev\) at CMP0067.cmake:[0-9]+ \(try_compile\):
+  Policy CMP0067 is not set: Honor language standard in try_compile\(\)
+  source-file signature.  Run "cmake --help-policy CMP0067" for policy
+  details.  Use the cmake_policy command to set the policy and suppress this
+  warning.
+
+  For compatibility with older versions of CMake, try_compile is not honoring
+  language standard variables in the test project:
+
+    CMAKE_C_STANDARD
+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+after try_compile with CMP0067 WARN-enabled
+before try_compile with CMP0067 OLD
+after try_compile with CMP0067 OLD
+before try_compile with CMP0067 NEW
+after try_compile with CMP0067 NEW

+ 40 - 0
Tests/RunCMake/try_compile/CMP0067.cmake

@@ -0,0 +1,40 @@
+enable_language(C)
+
+set(CMAKE_POLICY_WARNING_CMP0067 ON)
+message("before try_compile with CMP0067 WARN-enabled but no variables")
+try_compile(RESULT ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  )
+message("after try_compile with CMP0067 WARN-enabled but no variables")
+set(CMAKE_POLICY_WARNING_CMP0067 OFF)
+
+#-----------------------------------------------------------------------------
+
+set(CMAKE_C_STANDARD 90)
+
+message("before try_compile with CMP0067 WARN-default")
+try_compile(RESULT ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  )
+message("after try_compile with CMP0067 WARN-default")
+
+set(CMAKE_POLICY_WARNING_CMP0067 ON)
+message("before try_compile with CMP0067 WARN-enabled")
+try_compile(RESULT ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  )
+message("after try_compile with CMP0067 WARN-enabled")
+
+cmake_policy(SET CMP0067 OLD)
+message("before try_compile with CMP0067 OLD")
+try_compile(RESULT ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  )
+message("after try_compile with CMP0067 OLD")
+
+cmake_policy(SET CMP0066 NEW)
+message("before try_compile with CMP0067 NEW")
+try_compile(RESULT ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  )
+message("after try_compile with CMP0067 NEW")

+ 12 - 0
Tests/RunCMake/try_compile/CStandardGNU.cmake

@@ -9,3 +9,15 @@ try_compile(result ${CMAKE_CURRENT_BINARY_DIR}
 if(NOT result)
   message(FATAL_ERROR "try_compile failed:\n${out}")
 endif()
+
+cmake_policy(SET CMP0067 NEW)
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED 1)
+set(CMAKE_C_EXTENSIONS 0)
+try_compile(result ${CMAKE_CURRENT_BINARY_DIR}
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/CStandardGNU.c
+  OUTPUT_VARIABLE out
+  )
+if(NOT result)
+  message(FATAL_ERROR "try_compile failed:\n${out}")
+endif()

+ 12 - 0
Tests/RunCMake/try_compile/CxxStandardGNU.cmake

@@ -9,3 +9,15 @@ try_compile(result ${CMAKE_CURRENT_BINARY_DIR}
 if(NOT result)
   message(FATAL_ERROR "try_compile failed:\n${out}")
 endif()
+
+cmake_policy(SET CMP0067 NEW)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED 1)
+set(CMAKE_CXX_EXTENSIONS 0)
+try_compile(result ${CMAKE_CURRENT_BINARY_DIR}
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/CxxStandardGNU.cxx
+  OUTPUT_VARIABLE out
+  )
+if(NOT result)
+  message(FATAL_ERROR "try_compile failed:\n${out}")
+endif()

+ 1 - 0
Tests/RunCMake/try_compile/RunCMakeTest.cmake

@@ -44,6 +44,7 @@ endif()
 
 run_cmake(CMP0056)
 run_cmake(CMP0066)
+run_cmake(CMP0067)
 
 if(RunCMake_GENERATOR MATCHES "Make|Ninja")
   # Use a single build tree for a few tests without cleaning.