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
diff --git a/Help/command/try_compile.rst b/Help/command/try_compile.rst
index ca8fc77..0bc2ca3 100644
--- a/Help/command/try_compile.rst
+++ b/Help/command/try_compile.rst
@@ -135,6 +135,7 @@
 * :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
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index e89ea3da..043fb5c 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -57,6 +57,7 @@
 .. 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>
 
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 4d4b9ff..3da9144 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -280,6 +280,7 @@
    /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
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 18dd9d7..22e8add 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -386,6 +386,7 @@
    /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
diff --git a/Help/policy/CMP0091.rst b/Help/policy/CMP0091.rst
new file mode 100644
index 0000000..5b7c4e3
--- /dev/null
+++ b/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
diff --git a/Help/prop_tgt/MSVC_RUNTIME_LIBRARY-VALUES.txt b/Help/prop_tgt/MSVC_RUNTIME_LIBRARY-VALUES.txt
new file mode 100644
index 0000000..2bf71a9
--- /dev/null
+++ b/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.
diff --git a/Help/prop_tgt/MSVC_RUNTIME_LIBRARY.rst b/Help/prop_tgt/MSVC_RUNTIME_LIBRARY.rst
new file mode 100644
index 0000000..1e3f5e9
--- /dev/null
+++ b/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.
diff --git a/Help/release/dev/msvc-runtime-library.rst b/Help/release/dev/msvc-runtime-library.rst
new file mode 100644
index 0000000..4dddac2
--- /dev/null
+++ b/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`.
diff --git a/Help/variable/CMAKE_MSVC_RUNTIME_LIBRARY.rst b/Help/variable/CMAKE_MSVC_RUNTIME_LIBRARY.rst
new file mode 100644
index 0000000..6ed68c9
--- /dev/null
+++ b/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.
diff --git a/Modules/Platform/Windows-Intel-Fortran.cmake b/Modules/Platform/Windows-Intel-Fortran.cmake
index 3981a09..f00a8e4 100644
--- a/Modules/Platform/Windows-Intel-Fortran.cmake
+++ b/Modules/Platform/Windows-Intel-Fortran.cmake
@@ -4,8 +4,34 @@
 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()
diff --git a/Modules/Platform/Windows-MSVC.cmake b/Modules/Platform/Windows-MSVC.cmake
index df3bd1e..4279a74 100644
--- a/Modules/Platform/Windows-MSVC.cmake
+++ b/Modules/Platform/Windows-MSVC.cmake
@@ -298,6 +298,14 @@
 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 @@
 
   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)
diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index dcb1ff5..897f7a8 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/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 @@
   "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 @@
       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 @@
       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)) {
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 67763d4..8b51834 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/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 @@
   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(
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index 02a6295..113dd35 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -267,7 +267,10 @@
          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)                                         \
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index dc9b6d2..9598a3f 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -304,6 +304,7 @@
     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);
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 3746965..4114a67 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -1995,6 +1995,10 @@
     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")
diff --git a/Tests/MSVCRuntimeLibrary/CMakeLists.txt b/Tests/MSVCRuntimeLibrary/CMakeLists.txt
new file mode 100644
index 0000000..b7a6e86
--- /dev/null
+++ b/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)
diff --git a/Tests/MSVCRuntimeLibrary/Fortran/CMakeLists.txt b/Tests/MSVCRuntimeLibrary/Fortran/CMakeLists.txt
new file mode 100644
index 0000000..169ba07
--- /dev/null
+++ b/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()
diff --git a/Tests/MSVCRuntimeLibrary/Fortran/verify.F90 b/Tests/MSVCRuntimeLibrary/Fortran/verify.F90
new file mode 100644
index 0000000..6fe5e05
--- /dev/null
+++ b/Tests/MSVCRuntimeLibrary/Fortran/verify.F90
@@ -0,0 +1 @@
+#include "../verify.h"
diff --git a/Tests/MSVCRuntimeLibrary/verify.c b/Tests/MSVCRuntimeLibrary/verify.c
new file mode 100644
index 0000000..741bca6
--- /dev/null
+++ b/Tests/MSVCRuntimeLibrary/verify.c
@@ -0,0 +1 @@
+#include "verify.h"
diff --git a/Tests/MSVCRuntimeLibrary/verify.cxx b/Tests/MSVCRuntimeLibrary/verify.cxx
new file mode 100644
index 0000000..741bca6
--- /dev/null
+++ b/Tests/MSVCRuntimeLibrary/verify.cxx
@@ -0,0 +1 @@
+#include "verify.h"
diff --git a/Tests/MSVCRuntimeLibrary/verify.h b/Tests/MSVCRuntimeLibrary/verify.h
new file mode 100644
index 0000000..58d65fe
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 68411d6..a8dcb4b 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -195,6 +195,9 @@
 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")
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-result.txt b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-stderr.txt b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW-stderr.txt
new file mode 100644
index 0000000..803058d
--- /dev/null
+++ b/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.$
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW.cmake b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW.cmake
new file mode 100644
index 0000000..c3ea2fd
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-NEW.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0091 NEW)
+include(CMP0091-common.cmake)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-OLD.cmake b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-OLD.cmake
new file mode 100644
index 0000000..734cc9f
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-OLD.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0091 OLD)
+include(CMP0091-common.cmake)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-WARN.cmake b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-WARN.cmake
new file mode 100644
index 0000000..26f86a0
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-WARN.cmake
@@ -0,0 +1,2 @@
+
+include(CMP0091-common.cmake)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-common.cmake b/Tests/RunCMake/MSVCRuntimeLibrary/CMP0091-common.cmake
new file mode 100644
index 0000000..7827d2a
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/CMakeLists.txt b/Tests/RunCMake/MSVCRuntimeLibrary/CMakeLists.txt
new file mode 100644
index 0000000..3e470a2
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.14)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/RunCMakeTest.cmake b/Tests/RunCMake/MSVCRuntimeLibrary/RunCMakeTest.cmake
new file mode 100644
index 0000000..fad18da
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/RunCMakeTest.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+run_cmake(CMP0091-WARN)
+run_cmake(CMP0091-OLD)
+run_cmake(CMP0091-NEW)
diff --git a/Tests/RunCMake/MSVCRuntimeLibrary/empty.c b/Tests/RunCMake/MSVCRuntimeLibrary/empty.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/MSVCRuntimeLibrary/empty.c
diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
index df253a9..988312b 100644
--- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
@@ -2,6 +2,7 @@
 
 run_cmake(VsCSharpCompilerOpts)
 run_cmake(ExplicitCMakeLists)
+run_cmake(RuntimeLibrary)
 run_cmake(SourceGroupCMakeLists)
 
 run_cmake(VsConfigurationType)
diff --git a/Tests/RunCMake/VS10Project/RuntimeLibrary-check.cmake b/Tests/RunCMake/VS10Project/RuntimeLibrary-check.cmake
new file mode 100644
index 0000000..6b43d47
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/VS10Project/RuntimeLibrary.cmake b/Tests/RunCMake/VS10Project/RuntimeLibrary.cmake
new file mode 100644
index 0000000..6c77a25
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/VS10Project/empty.c b/Tests/RunCMake/VS10Project/empty.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/empty.c
diff --git a/Tests/RunCMake/VS10Project/empty.cxx b/Tests/RunCMake/VS10Project/empty.cxx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/empty.cxx