Browse Source

MSVC: Add abstraction for runtime library selection

Replace our hard-coded defaults for `/MD` and `/MDd` with a first-class
abstraction to select the runtime library from an enumeration of logical
names.  We've long hesitated to do this because the idea of "runtime
library selection" touches on related concepts on several platforms.
Avoid that scope creep by simply defining an abstraction that applies
only when targeting the MSVC ABI on Windows.

Removing the old default flags requires a policy because existing
projects may rely on string processing to edit them and choose a runtime
library under the old behavior.  Add policy CMP0091 to provide
compatibility.

Fixes: #19108
Brad King 6 years ago
parent
commit
fb3370b6a1
37 changed files with 478 additions and 13 deletions
  1. 1 0
      Help/command/try_compile.rst
  2. 1 0
      Help/manual/cmake-policies.7.rst
  3. 1 0
      Help/manual/cmake-properties.7.rst
  4. 1 0
      Help/manual/cmake-variables.7.rst
  5. 47 0
      Help/policy/CMP0091.rst
  6. 15 0
      Help/prop_tgt/MSVC_RUNTIME_LIBRARY-VALUES.txt
  7. 26 0
      Help/prop_tgt/MSVC_RUNTIME_LIBRARY.rst
  8. 7 0
      Help/release/dev/msvc-runtime-library.rst
  9. 27 0
      Help/variable/CMAKE_MSVC_RUNTIME_LIBRARY.rst
  10. 28 2
      Modules/Platform/Windows-Intel-Fortran.cmake
  11. 30 8
      Modules/Platform/Windows-MSVC.cmake
  12. 11 0
      Source/cmCoreTryCompile.cxx
  13. 35 2
      Source/cmLocalGenerator.cxx
  14. 4 1
      Source/cmPolicies.h
  15. 1 0
      Source/cmTarget.cxx
  16. 4 0
      Tests/CMakeLists.txt
  17. 49 0
      Tests/MSVCRuntimeLibrary/CMakeLists.txt
  18. 50 0
      Tests/MSVCRuntimeLibrary/Fortran/CMakeLists.txt
  19. 1 0
      Tests/MSVCRuntimeLibrary/Fortran/verify.F90
  20. 1 0
      Tests/MSVCRuntimeLibrary/verify.c
  21. 1 0
      Tests/MSVCRuntimeLibrary/verify.cxx
  22. 29 0
      Tests/MSVCRuntimeLibrary/verify.h
  23. 3 0
      Tests/RunCMake/CMakeLists.txt
  24. 1 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-result.txt
  25. 2 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-stderr.txt
  26. 2 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW.cmake
  27. 2 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-OLD.cmake
  28. 2 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-WARN.cmake
  29. 37 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-common.cmake
  30. 3 0
      Tests/RunCMake/MSVCRuntimeLibrary/CMakeLists.txt
  31. 5 0
      Tests/RunCMake/MSVCRuntimeLibrary/RunCMakeTest.cmake
  32. 0 0
      Tests/RunCMake/MSVCRuntimeLibrary/empty.c
  33. 1 0
      Tests/RunCMake/VS10Project/RunCMakeTest.cmake
  34. 34 0
      Tests/RunCMake/VS10Project/RuntimeLibrary-check.cmake
  35. 16 0
      Tests/RunCMake/VS10Project/RuntimeLibrary.cmake
  36. 0 0
      Tests/RunCMake/VS10Project/empty.c
  37. 0 0
      Tests/RunCMake/VS10Project/empty.cxx

+ 1 - 0
Help/command/try_compile.rst

@@ -135,6 +135,7 @@ default values:
 * :variable:`CMAKE_ENABLE_EXPORTS`
 * :variable:`CMAKE_LINK_SEARCH_START_STATIC`
 * :variable:`CMAKE_LINK_SEARCH_END_STATIC`
+* :variable:`CMAKE_MSVC_RUNTIME_LIBRARY`
 * :variable:`CMAKE_POSITION_INDEPENDENT_CODE`
 
 If :policy:`CMP0056` is set to ``NEW``, then

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

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.15
 .. toctree::
    :maxdepth: 1
 
+   CMP0091: MSVC runtime library flags are selected by an abstraction. </policy/CMP0091>
    CMP0090: export(PACKAGE) does not populate package registry by default. </policy/CMP0090>
    CMP0089: Compiler id for IBM Clang-based XL compilers is now XLClang. </policy/CMP0089>
 

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

@@ -280,6 +280,7 @@ Properties on Targets
    /prop_tgt/MACOSX_RPATH
    /prop_tgt/MANUALLY_ADDED_DEPENDENCIES
    /prop_tgt/MAP_IMPORTED_CONFIG_CONFIG
+   /prop_tgt/MSVC_RUNTIME_LIBRARY
    /prop_tgt/NAME
    /prop_tgt/NO_SONAME
    /prop_tgt/NO_SYSTEM_FROM_IMPORTED

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

@@ -386,6 +386,7 @@ Variables that Control the Build
    /variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG_INIT
    /variable/CMAKE_MODULE_LINKER_FLAGS_INIT
    /variable/CMAKE_MSVCIDE_RUN_PATH
+   /variable/CMAKE_MSVC_RUNTIME_LIBRARY
    /variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX
    /variable/CMAKE_NO_BUILTIN_CHRPATH
    /variable/CMAKE_NO_SYSTEM_FROM_IMPORTED

+ 47 - 0
Help/policy/CMP0091.rst

@@ -0,0 +1,47 @@
+CMP0091
+-------
+
+MSVC runtime library flags are selected by an abstraction.
+
+Compilers targeting the MSVC ABI have flags to select the MSVC runtime library.
+Runtime library selection typically varies with build configuration because
+there is a separate runtime library for Debug builds.
+
+In CMake 3.14 and below, MSVC runtime library selection flags are added to
+the default :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` cache entries by CMake
+automatically.  This allows users to edit their cache entries to adjust the
+flags.  However, the presence of such default flags is problematic for
+projects that want to choose a different runtime library programmatically.
+In particular, it requires string editing of the
+:variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` variables with knowledge of the
+CMake builtin defaults so they can be replaced.
+
+CMake 3.15 and above prefer to leave the MSVC runtime library selection flags
+out of the default :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` values and instead
+offer a first-class abstraction.  The :variable:`CMAKE_MSVC_RUNTIME_LIBRARY`
+variable and :prop_tgt:`MSVC_RUNTIME_LIBRARY` target property may be set to
+select the MSVC runtime library.
+
+This policy provides compatibility with projects that have not been updated
+to be aware of the abstraction.  The policy setting takes effect as of the
+first :command:`project` or :command:`enable_language` command that enables
+a language whose compiler targets the MSVC ABI.
+
+.. note::
+
+  Once the policy has taken effect at the top of a project, that choice
+  must be used throughout the tree.  In projects that have nested projects
+  in subdirectories, be sure to convert everything together.
+
+The ``OLD`` behavior for this policy is to place MSVC runtime library
+flags in the default :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` cache
+entries and ignore the :variable:`CMAKE_MSVC_RUNTIME_LIBRARY` abstraction.
+The ``NEW`` behavior for this policy is to *not* place MSVC runtime
+library flags in the default cache entries and use the abstraction instead.
+
+This policy was introduced in CMake version 3.15.  Use the
+:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
+Unlike many policies, CMake version |release| does *not* warn
+when this policy is not set and simply uses ``OLD`` behavior.
+
+.. include:: DEPRECATED.txt

+ 15 - 0
Help/prop_tgt/MSVC_RUNTIME_LIBRARY-VALUES.txt

@@ -0,0 +1,15 @@
+``MultiThreaded``
+  Compile with ``-MT`` or equivalent flag(s) to use a multi-threaded
+  statically-linked runtime library.
+``MultiThreadedDLL``
+  Compile with ``-MD`` or equivalent flag(s) to use a multi-threaded
+  dynamically-linked runtime library.
+``MultiThreadedDebug``
+  Compile with ``-MTd`` or equivalent flag(s) to use a multi-threaded
+  statically-linked runtime library.
+``MultiThreadedDebugDLL``
+  Compile with ``-MDd`` or equivalent flag(s) to use a multi-threaded
+  dynamically-linked runtime library.
+
+The value is ignored on non-MSVC compilers but an unsupported value will
+be rejected as an error when using a compiler targeting the MSVC ABI.

+ 26 - 0
Help/prop_tgt/MSVC_RUNTIME_LIBRARY.rst

@@ -0,0 +1,26 @@
+MSVC_RUNTIME_LIBRARY
+--------------------
+
+Select the MSVC runtime library for use by compilers targeting the MSVC ABI.
+
+The allowed values are:
+
+.. include:: MSVC_RUNTIME_LIBRARY-VALUES.txt
+
+Use :manual:`generator expressions <cmake-generator-expressions(7)>` to
+support per-configuration specification.  For example, the code:
+
+.. code-block:: cmake
+
+  add_executable(foo foo.c)
+  set_property(TARGET foo PROPERTY
+    MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+
+selects for the target ``foo`` a multi-threaded statically-linked runtime
+library with or without debug information depending on the configuration.
+
+.. note::
+
+  This property has effect only when policy :policy:`CMP0091` is set to ``NEW``
+  prior to the first :command:`project` or :command:`enable_language` command
+  that enables a language using a compiler targeting the MSVC ABI.

+ 7 - 0
Help/release/dev/msvc-runtime-library.rst

@@ -0,0 +1,7 @@
+msvc-runtime-library
+--------------------
+
+* The :variable:`CMAKE_MSVC_RUNTIME_LIBRARY` variable and
+  :prop_tgt:`MSVC_RUNTIME_LIBRARY` target property were introduced to
+  select the runtime library used by compilers targeting the MSVC ABI.
+  See policy :policy:`CMP0091`.

+ 27 - 0
Help/variable/CMAKE_MSVC_RUNTIME_LIBRARY.rst

@@ -0,0 +1,27 @@
+CMAKE_MSVC_RUNTIME_LIBRARY
+--------------------------
+
+Select the MSVC runtime library for use by compilers targeting the MSVC ABI.
+This variable is used to initialize the :prop_tgt:`MSVC_RUNTIME_LIBRARY`
+property on all targets as they are created.  It is also propagated by
+calls to the :command:`try_compile` command into the test project.
+
+The allowed values are:
+
+.. include:: ../prop_tgt/MSVC_RUNTIME_LIBRARY-VALUES.txt
+
+Use :manual:`generator expressions <cmake-generator-expressions(7)>` to
+support per-configuration specification.  For example, the code:
+
+.. code-block:: cmake
+
+  set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+
+selects for all following targets a multi-threaded statically-linked runtime
+library with or without debug information depending on the configuration.
+
+.. note::
+
+  This variable has effect only when policy :policy:`CMP0091` is set to ``NEW``
+  prior to the first :command:`project` or :command:`enable_language` command
+  that enables a language using a compiler targeting the MSVC ABI.

+ 28 - 2
Modules/Platform/Windows-Intel-Fortran.cmake

@@ -4,8 +4,34 @@ set(_COMPILE_Fortran " /fpp")
 set(CMAKE_Fortran_MODDIR_FLAG "-module:")
 set(CMAKE_Fortran_STANDARD_LIBRARIES_INIT "user32.lib")
 __windows_compiler_intel(Fortran)
-string(APPEND CMAKE_Fortran_FLAGS_INIT " /W1 /nologo /fpp /libs:dll /threads")
-string(APPEND CMAKE_Fortran_FLAGS_DEBUG_INIT " /Od /debug:full /dbglibs")
+if(CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)
+  set(_LIBSDLL "")
+  set(_DBGLIBS "")
+  set(_THREADS "")
+else()
+  set(_LIBSDLL " /libs:dll")
+  set(_DBGLIBS " /dbglibs")
+  set(_THREADS " /threads")
+endif()
+string(APPEND CMAKE_Fortran_FLAGS_INIT " /W1 /nologo /fpp${_LIBSDLL}${_THREADS}")
+string(APPEND CMAKE_Fortran_FLAGS_DEBUG_INIT " /Od /debug:full${_DBGLIBS}")
 string(APPEND CMAKE_Fortran_FLAGS_MINSIZEREL_INIT " /O1 /DNDEBUG")
 string(APPEND CMAKE_Fortran_FLAGS_RELEASE_INIT " /O2 /DNDEBUG")
 string(APPEND CMAKE_Fortran_FLAGS_RELWITHDEBINFO_INIT " /O2 /debug:full /DNDEBUG")
+unset(_LIBSDLL)
+unset(_DBGLIBS)
+unset(_THREADS)
+
+set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreaded         -threads -libs:static)
+set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL      -threads -libs:dll)
+set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug    -threads -libs:static -dbglibs)
+set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL -threads -libs:dll    -dbglibs)
+
+# Intel Fortran for Windows supports single-threaded RTL but it is
+# not implemented by the Visual Studio integration.
+if(NOT CMAKE_GENERATOR MATCHES "Visual Studio")
+  set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_SingleThreaded                 -libs:static)
+  set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_SingleThreadedDLL              -libs:dll)
+  set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_SingleThreadedDebug            -libs:static -dbglibs)
+  set(CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_SingleThreadedDebugDLL         -libs:dll    -dbglibs)
+endif()

+ 30 - 8
Modules/Platform/Windows-MSVC.cmake

@@ -298,6 +298,14 @@ endforeach()
 string(APPEND CMAKE_STATIC_LINKER_FLAGS_INIT " ${_MACHINE_ARCH_FLAG}")
 unset(_MACHINE_ARCH_FLAG)
 
+cmake_policy(GET CMP0091 __WINDOWS_MSVC_CMP0091)
+if(__WINDOWS_MSVC_CMP0091 STREQUAL "NEW")
+  set(CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
+else()
+  set(CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT "")
+endif()
+unset(__WINDOWS_MSVC_CMP0091)
+
 macro(__windows_compiler_msvc lang)
   if(NOT MSVC_VERSION LESS 1400)
     # for 2005 make sure the manifest is put in the dll with mt
@@ -351,21 +359,35 @@ macro(__windows_compiler_msvc lang)
 
   if("x${lang}" STREQUAL "xC" OR
       "x${lang}" STREQUAL "xCXX")
+    if(CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)
+      set(_MDd "")
+      set(_MD "")
+    else()
+      set(_MDd " /MDd")
+      set(_MD " /MD")
+    endif()
     if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "v[0-9]+_clang_.*")
       # note: MSVC 14 2015 Update 1 sets -fno-ms-compatibility by default, but this does not allow one to compile many projects
       # that include MS's own headers. CMake itself is affected project too.
       string(APPEND CMAKE_${lang}_FLAGS_INIT " ${_PLATFORM_DEFINES}${_PLATFORM_DEFINES_${lang}} -fms-extensions -fms-compatibility -D_WINDOWS -Wall${_FLAGS_${lang}}")
-      string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT " /MDd -gline-tables-only -fno-inline -O0 ${_RTC1}")
-      string(APPEND CMAKE_${lang}_FLAGS_RELEASE_INIT " /MD -O2 -DNDEBUG")
-      string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " /MD -gline-tables-only -O2 -fno-inline -DNDEBUG")
-      string(APPEND CMAKE_${lang}_FLAGS_MINSIZEREL_INIT " /MD -DNDEBUG") # TODO: Add '-Os' once VS generator maps it properly for Clang
+      string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT "${_MDd} -gline-tables-only -fno-inline -O0 ${_RTC1}")
+      string(APPEND CMAKE_${lang}_FLAGS_RELEASE_INIT "${_MD} -O2 -DNDEBUG")
+      string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT "${_MD} -gline-tables-only -O2 -fno-inline -DNDEBUG")
+      string(APPEND CMAKE_${lang}_FLAGS_MINSIZEREL_INIT "${_MD} -DNDEBUG") # TODO: Add '-Os' once VS generator maps it properly for Clang
     else()
       string(APPEND CMAKE_${lang}_FLAGS_INIT " ${_PLATFORM_DEFINES}${_PLATFORM_DEFINES_${lang}} /D_WINDOWS /W3${_FLAGS_${lang}}")
-      string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT " /MDd /Zi /Ob0 /Od ${_RTC1}")
-      string(APPEND CMAKE_${lang}_FLAGS_RELEASE_INIT " /MD /O2 /Ob2 /DNDEBUG")
-      string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " /MD /Zi /O2 /Ob1 /DNDEBUG")
-      string(APPEND CMAKE_${lang}_FLAGS_MINSIZEREL_INIT " /MD /O1 /Ob1 /DNDEBUG")
+      string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT "${_MDd} /Zi /Ob0 /Od ${_RTC1}")
+      string(APPEND CMAKE_${lang}_FLAGS_RELEASE_INIT "${_MD} /O2 /Ob2 /DNDEBUG")
+      string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT "${_MD} /Zi /O2 /Ob1 /DNDEBUG")
+      string(APPEND CMAKE_${lang}_FLAGS_MINSIZEREL_INIT "${_MD} /O1 /Ob1 /DNDEBUG")
     endif()
+    unset(_MDd)
+    unset(_MD)
+
+    set(CMAKE_${lang}_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreaded         -MT)
+    set(CMAKE_${lang}_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL      -MD)
+    set(CMAKE_${lang}_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug    -MTd)
+    set(CMAKE_${lang}_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL -MDd)
   endif()
   set(CMAKE_${lang}_LINKER_SUPPORTS_PDB ON)
   set(CMAKE_NINJA_DEPTYPE_${lang} msvc)

+ 11 - 0
Source/cmCoreTryCompile.cxx

@@ -20,6 +20,7 @@
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmVersion.h"
+#include "cm_static_string_view.hxx"
 #include "cmake.h"
 
 static std::string const kCMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN =
@@ -42,6 +43,8 @@ static std::string const kCMAKE_LINK_SEARCH_END_STATIC =
   "CMAKE_LINK_SEARCH_END_STATIC";
 static std::string const kCMAKE_LINK_SEARCH_START_STATIC =
   "CMAKE_LINK_SEARCH_START_STATIC";
+static std::string const kCMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT =
+  "CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT";
 static std::string const kCMAKE_OSX_ARCHITECTURES = "CMAKE_OSX_ARCHITECTURES";
 static std::string const kCMAKE_OSX_DEPLOYMENT_TARGET =
   "CMAKE_OSX_DEPLOYMENT_TARGET";
@@ -500,6 +503,13 @@ int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
       fprintf(fout, "set(CMAKE_MODULE_PATH \"%s\")\n", def);
     }
 
+    /* Set MSVC runtime library policy to match our selection.  */
+    if (const char* msvcRuntimeLibraryDefault =
+          this->Makefile->GetDefinition(kCMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)) {
+      fprintf(fout, "cmake_policy(SET CMP0091 %s)\n",
+              *msvcRuntimeLibraryDefault ? "NEW" : "OLD");
+    }
+
     std::string projectLangs;
     for (std::string const& li : testLangs) {
       projectLangs += " " + li;
@@ -660,6 +670,7 @@ int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv,
       vars.insert(kCMAKE_SYSROOT_COMPILE);
       vars.insert(kCMAKE_SYSROOT_LINK);
       vars.insert(kCMAKE_WARN_DEPRECATED);
+      vars.emplace("CMAKE_MSVC_RUNTIME_LIBRARY"_s);
 
       if (const char* varListStr = this->Makefile->GetDefinition(
             kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES)) {

+ 35 - 2
Source/cmLocalGenerator.cxx

@@ -6,6 +6,7 @@
 #include "cmComputeLinkInformation.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorExpression.h"
 #include "cmGeneratorExpressionEvaluationFile.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
@@ -1519,8 +1520,40 @@ void cmLocalGenerator::AddLanguageFlags(std::string& flags,
   flagsVar += "_FLAGS";
   this->AddConfigVariableFlags(flags, flagsVar, config);
 
-  // Placeholder for possible future per-target flags.
-  static_cast<void>(target);
+  // Add MSVC runtime library flags.  This is activated by the presence
+  // of a default selection whether or not it is overridden by a property.
+  const char* msvcRuntimeLibraryDefault =
+    this->Makefile->GetDefinition("CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT");
+  if (msvcRuntimeLibraryDefault && *msvcRuntimeLibraryDefault) {
+    const char* msvcRuntimeLibraryValue =
+      target->GetProperty("MSVC_RUNTIME_LIBRARY");
+    if (!msvcRuntimeLibraryValue) {
+      msvcRuntimeLibraryValue = msvcRuntimeLibraryDefault;
+    }
+    cmGeneratorExpression ge;
+    std::unique_ptr<cmCompiledGeneratorExpression> cge =
+      ge.Parse(msvcRuntimeLibraryValue);
+    std::string const msvcRuntimeLibrary =
+      cge->Evaluate(this, config, false, target);
+    if (!msvcRuntimeLibrary.empty()) {
+      if (const char* msvcRuntimeLibraryOptions =
+            this->Makefile->GetDefinition(
+              "CMAKE_" + lang + "_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_" +
+              msvcRuntimeLibrary)) {
+        this->AppendCompileOptions(flags, msvcRuntimeLibraryOptions);
+      } else if ((this->Makefile->GetSafeDefinition(
+                    "CMAKE_" + lang + "_COMPILER_ID") == "MSVC" ||
+                  this->Makefile->GetSafeDefinition(
+                    "CMAKE_" + lang + "_SIMULATE_ID") == "MSVC") &&
+                 !cmSystemTools::GetErrorOccuredFlag()) {
+        // The compiler uses the MSVC ABI so it needs a known runtime library.
+        this->IssueMessage(MessageType::FATAL_ERROR,
+                           "MSVC_RUNTIME_LIBRARY value '" +
+                             msvcRuntimeLibrary + "' not known for this " +
+                             lang + " compiler.");
+      }
+    }
+  }
 }
 
 void cmLocalGenerator::AddLanguageFlagsForLinking(

+ 4 - 1
Source/cmPolicies.h

@@ -267,7 +267,10 @@ class cmMakefile;
          15, 0, cmPolicies::WARN)                                             \
   SELECT(POLICY, CMP0090,                                                     \
          "export(PACKAGE) does not populate package registry by default.", 3, \
-         15, 0, cmPolicies::WARN)
+         15, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0091,                                                     \
+         "MSVC runtime library flags are selected by an abstraction.", 3, 15, \
+         0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 1 - 0
Source/cmTarget.cxx

@@ -304,6 +304,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     InitProperty("AUTORCC_OPTIONS", nullptr);
     InitProperty("LINK_DEPENDS_NO_SHARED", nullptr);
     InitProperty("LINK_INTERFACE_LIBRARIES", nullptr);
+    InitProperty("MSVC_RUNTIME_LIBRARY", nullptr);
     InitProperty("WIN32_EXECUTABLE", nullptr);
     InitProperty("MACOSX_BUNDLE", nullptr);
     InitProperty("MACOSX_RPATH", nullptr);

+ 4 - 0
Tests/CMakeLists.txt

@@ -1995,6 +1995,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
     if(NOT CMAKE_C_COMPILER_ID STREQUAL "Clang")
       ADD_TEST_MACRO(PrecompiledHeader foo)
     endif()
+    ADD_TEST_MACRO(MSVCRuntimeLibrary)
+    if(CMAKE_Fortran_COMPILER)
+      ADD_TEST_MACRO(MSVCRuntimeLibrary.Fortran)
+    endif()
   endif()
   if(MSVC OR
       "${CMAKE_GENERATOR}" MATCHES "(MSYS|MinGW) Makefiles")

+ 49 - 0
Tests/MSVCRuntimeLibrary/CMakeLists.txt

@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.14)
+cmake_policy(SET CMP0091 NEW)
+project(MSVCRuntimeLibrary)
+
+function(verify_combinations threads lang src)
+  set(verify_tc_config_ Release)
+  set(verify_tc_config_Debug Debug)
+  set(verify_def_MultiThreaded -DVERIFY_MT)
+  set(verify_def_Debug -DVERIFY_DEBUG)
+  set(verify_def_DLL -DVERIFY_DLL)
+  foreach(dbg "" Debug)
+    foreach(dll "" DLL)
+      # Construct the name of this runtime library combination.
+      set(rtl "${threads}${dbg}${dll}")
+
+      # Test that try_compile builds with this RTL.
+      set(CMAKE_MSVC_RUNTIME_LIBRARY "${rtl}")
+      set(CMAKE_TRY_COMPILE_CONFIGURATION "${verify_tc_config_${dbg}}")
+      set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
+      try_compile(${rtl}_COMPILES
+        ${CMAKE_CURRENT_BINARY_DIR}/try_compile/${rtl}
+        ${CMAKE_CURRENT_SOURCE_DIR}/${src}
+        COMPILE_DEFINITIONS ${verify_def_${threads}} ${verify_def_${dbg}} ${verify_def_${dll}}
+        OUTPUT_VARIABLE ${rtl}_OUTPUT
+        )
+      if(${rtl}_COMPILES)
+        message(STATUS "try_compile with ${rtl} worked")
+      else()
+        string(REPLACE "\n" "\n  " ${rtl}_OUTPUT "  ${${rtl}_OUTPUT}")
+        message(SEND_ERROR "try_compile with ${rtl} failed:\n${${rtl}_OUTPUT}")
+      endif()
+
+      # Test that targets build with this RTL.
+      set(CMAKE_MSVC_RUNTIME_LIBRARY "$<$<BOOL:$<TARGET_PROPERTY:BOOL_TRUE>>:${rtl}>$<$<BOOL:$<TARGET_PROPERTY:BOOL_FALSE>>:BadContent>")
+      add_library(${rtl}-${lang} ${src})
+      set_property(TARGET ${rtl}-${lang} PROPERTY BOOL_TRUE TRUE)
+      target_compile_definitions(${rtl}-${lang} PRIVATE ${verify_def_${threads}} ${verify_def_${dbg}} ${verify_def_${dll}})
+    endforeach()
+  endforeach()
+endfunction()
+
+function(verify lang src)
+  add_library(default-${lang} ${src})
+  target_compile_definitions(default-${lang} PRIVATE VERIFY_MT VERIFY_DLL "$<$<CONFIG:Debug>:VERIFY_DEBUG>")
+  verify_combinations(MultiThreaded ${lang} ${src})
+endfunction()
+
+verify(C verify.c)
+verify(CXX verify.cxx)

+ 50 - 0
Tests/MSVCRuntimeLibrary/Fortran/CMakeLists.txt

@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.14)
+cmake_policy(SET CMP0091 NEW)
+project(MSVCRuntimeLibraryFortran Fortran)
+
+foreach(t MultiThreaded SingleThreaded)
+  foreach(dbg "" Debug)
+    foreach(dll "" DLL)
+      set(var "CMAKE_Fortran_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_${t}${dbg}${dll}")
+      # ifort does not actually define these, so inject them
+      string(REPLACE "-threads" "-threads;-D_MT" "${var}" "${${var}}")
+      string(REPLACE "-dbglibs" "-dbglibs;-D_DEBUG" "${var}" "${${var}}")
+    endforeach()
+  endforeach()
+endforeach()
+string(APPEND CMAKE_Fortran_FLAGS " -w")
+
+function(verify_combinations threads lang src)
+  set(verify_tc_config_ Release)
+  set(verify_tc_config_Debug Debug)
+  set(verify_def_MultiThreaded -DVERIFY_MT)
+  set(verify_def_Debug -DVERIFY_DEBUG)
+  set(verify_def_DLL -DVERIFY_DLL)
+  foreach(dbg "" Debug)
+    foreach(dll "" DLL)
+      # Construct the name of this runtime library combination.
+      set(rtl "${threads}${dbg}${dll}")
+
+      # Test that targets build with this RTL.
+      set(CMAKE_MSVC_RUNTIME_LIBRARY "$<$<BOOL:$<TARGET_PROPERTY:BOOL_TRUE>>:${rtl}>$<$<BOOL:$<TARGET_PROPERTY:BOOL_FALSE>>:BadContent>")
+      add_library(${rtl}-${lang} ${src})
+      set_property(TARGET ${rtl}-${lang} PROPERTY BOOL_TRUE TRUE)
+      target_compile_definitions(${rtl}-${lang} PRIVATE ${verify_def_${threads}} ${verify_def_${dbg}} ${verify_def_${dll}})
+    endforeach()
+  endforeach()
+endfunction()
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+function(verify lang src)
+  add_library(default-${lang} ${src})
+  target_compile_definitions(default-${lang} PRIVATE VERIFY_MT VERIFY_DLL "$<$<CONFIG:Debug>:VERIFY_DEBUG>")
+  verify_combinations(MultiThreaded ${lang} ${src})
+endfunction()
+
+verify(Fortran verify.F90)
+# Intel Fortran for Windows supports single-threaded RTL but it is
+# not implemented by the Visual Studio integration.
+if(NOT CMAKE_GENERATOR MATCHES "Visual Studio")
+  verify_combinations(SingleThreaded Fortran verify.F90)
+endif()

+ 1 - 0
Tests/MSVCRuntimeLibrary/Fortran/verify.F90

@@ -0,0 +1 @@
+#include "../verify.h"

+ 1 - 0
Tests/MSVCRuntimeLibrary/verify.c

@@ -0,0 +1 @@
+#include "verify.h"

+ 1 - 0
Tests/MSVCRuntimeLibrary/verify.cxx

@@ -0,0 +1 @@
+#include "verify.h"

+ 29 - 0
Tests/MSVCRuntimeLibrary/verify.h

@@ -0,0 +1,29 @@
+#ifdef VERIFY_DEBUG
+#  ifndef _DEBUG
+#    error "_DEBUG not defined by debug runtime library selection"
+#  endif
+#else
+#  ifdef _DEBUG
+#    error "_DEBUG defined by non-debug runtime library selection"
+#  endif
+#endif
+
+#ifdef VERIFY_DLL
+#  ifndef _DLL
+#    error "_DLL not defined by DLL runtime library selection"
+#  endif
+#else
+#  ifdef _DLL
+#    error "_DLL defined by non-DLL runtime library selection"
+#  endif
+#endif
+
+#ifdef VERIFY_MT
+#  ifndef _MT
+#    error "_MT not defined by multi-threaded runtime library selection"
+#  endif
+#else
+#  ifdef _MT
+#    error "_MT defined by single-threaded runtime library selection"
+#  endif
+#endif

+ 3 - 0
Tests/RunCMake/CMakeLists.txt

@@ -195,6 +195,9 @@ add_RunCMake_test(LinkStatic)
 if(CMAKE_CXX_COMPILER_ID MATCHES "^(Cray|PGI|XL|XLClang)$")
   add_RunCMake_test(MetaCompileFeatures)
 endif()
+if(MSVC)
+  add_RunCMake_test(MSVCRuntimeLibrary)
+endif()
 add_RunCMake_test(ObjectLibrary)
 add_RunCMake_test(ParseImplicitIncludeInfo)
 if(UNIX AND CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG AND CMAKE_EXECUTABLE_FORMAT STREQUAL "ELF")

+ 1 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error in CMakeLists.txt:
+  MSVC_RUNTIME_LIBRARY value 'BogusValue' not known for this C compiler.$

+ 2 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0091 NEW)
+include(CMP0091-common.cmake)

+ 2 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0091 OLD)
+include(CMP0091-common.cmake)

+ 2 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-WARN.cmake

@@ -0,0 +1,2 @@
+
+include(CMP0091-common.cmake)

+ 37 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-common.cmake

@@ -0,0 +1,37 @@
+enable_language(C)
+
+cmake_policy(GET CMP0091 cmp0091)
+if(cmp0091 STREQUAL "NEW")
+  if(NOT CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)
+    message(SEND_ERROR "CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT not set under NEW behavior")
+  endif()
+else()
+  if(CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)
+    message(SEND_ERROR "CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT is set under OLD behavior")
+  endif()
+endif()
+
+if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+  if(CMAKE_C_FLAGS_DEBUG MATCHES "[/-]MDd( |$)")
+    set(have_MDd 1)
+  else()
+    set(have_MDd 0)
+  endif()
+  if(CMAKE_C_FLAGS_RELEASE MATCHES "[/-]MD( |$)")
+    set(have_MD 1)
+  else()
+    set(have_MD 0)
+  endif()
+  if(cmp0091 STREQUAL "NEW")
+    if(have_MDd OR have_MD)
+      message(SEND_ERROR "Have a -MD* flag under NEW behavior.")
+    endif()
+  else()
+    if(NOT (have_MDd AND have_MD))
+      message(SEND_ERROR "Do not have -MD* flags under OLD behavior.")
+    endif()
+  endif()
+endif()
+
+set(CMAKE_MSVC_RUNTIME_LIBRARY BogusValue)
+add_library(foo empty.c)

+ 3 - 0
Tests/RunCMake/MSVCRuntimeLibrary/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.14)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 5 - 0
Tests/RunCMake/MSVCRuntimeLibrary/RunCMakeTest.cmake

@@ -0,0 +1,5 @@
+include(RunCMake)
+
+run_cmake(CMP0091-WARN)
+run_cmake(CMP0091-OLD)
+run_cmake(CMP0091-NEW)

+ 0 - 0
Tests/RunCMake/MSVCRuntimeLibrary/empty.c


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

@@ -2,6 +2,7 @@ include(RunCMake)
 
 run_cmake(VsCSharpCompilerOpts)
 run_cmake(ExplicitCMakeLists)
+run_cmake(RuntimeLibrary)
 run_cmake(SourceGroupCMakeLists)
 
 run_cmake(VsConfigurationType)

+ 34 - 0
Tests/RunCMake/VS10Project/RuntimeLibrary-check.cmake

@@ -0,0 +1,34 @@
+macro(RuntimeLibrary_check tgt rtl_expect)
+  set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/${tgt}.vcxproj")
+  if(NOT EXISTS "${vcProjectFile}")
+    set(RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj does not exist.")
+    return()
+  endif()
+
+  set(HAVE_Runtimelibrary 0)
+
+  file(STRINGS "${vcProjectFile}" lines)
+  foreach(line IN LISTS lines)
+    if(line MATCHES "^ *<RuntimeLibrary>([^<>]+)</RuntimeLibrary>")
+      set(rtl_actual "${CMAKE_MATCH_1}")
+      if(NOT "${rtl_actual}" STREQUAL "${rtl_expect}")
+        set(RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj has RuntimeLibrary '${rtl_actual}', not '${rtl_expect}'.")
+        return()
+      endif()
+      set(HAVE_Runtimelibrary 1)
+      break()
+    endif()
+  endforeach()
+
+  if(NOT HAVE_Runtimelibrary)
+    set(RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj does not have a RuntimeLibrary field.")
+    return()
+  endif()
+endmacro()
+
+RuntimeLibrary_check(default-C MultiThreadedDebugDLL)
+RuntimeLibrary_check(default-CXX MultiThreadedDebugDLL)
+RuntimeLibrary_check(MTd-C MultiThreadedDebug)
+RuntimeLibrary_check(MTd-CXX MultiThreadedDebug)
+RuntimeLibrary_check(MT-C MultiThreaded)
+RuntimeLibrary_check(MT-CXX MultiThreaded)

+ 16 - 0
Tests/RunCMake/VS10Project/RuntimeLibrary.cmake

@@ -0,0 +1,16 @@
+set(CMAKE_CONFIGURATION_TYPES Debug)
+cmake_policy(SET CMP0091 NEW)
+enable_language(C)
+enable_language(CXX)
+
+add_library(default-C empty.c)
+add_library(default-CXX empty.cxx)
+
+set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebug")
+add_library(MTd-C empty.c)
+add_library(MTd-CXX empty.cxx)
+
+add_library(MT-C empty.c)
+set_property(TARGET MT-C PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
+add_library(MT-CXX empty.cxx)
+set_property(TARGET MT-CXX PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")

+ 0 - 0
Tests/RunCMake/VS10Project/empty.c


+ 0 - 0
Tests/RunCMake/VS10Project/empty.cxx