Merge topic 'patch-FindPackageMessage'

ee398e8946 FindPackageMessage: Update documentation

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !10441
diff --git a/.gitlab/ci/configure_macos_common.cmake b/.gitlab/ci/configure_macos_common.cmake
index e78c9ce..614f211 100644
--- a/.gitlab/ci/configure_macos_common.cmake
+++ b/.gitlab/ci/configure_macos_common.cmake
@@ -16,3 +16,9 @@
 # when CMake itself is configured.  Use a version that is not
 # newer than the macOS version running on any CI host.
 set(CMake_TEST_XCTest_DEPLOYMENT_TARGET "10.15" CACHE STRING "")
+
+if("$ENV{CMAKE_CONFIGURATION}" MATCHES "macos_arm64")
+  set(CMake_TEST_APPLE_SILICON ON CACHE BOOL "")
+else()
+  set(CMake_TEST_APPLE_SILICON OFF CACHE BOOL "")
+endif()
diff --git a/Help/command/find_package.rst b/Help/command/find_package.rst
index b7232d6..089a3d6 100644
--- a/Help/command/find_package.rst
+++ b/Help/command/find_package.rst
@@ -956,3 +956,37 @@
 
 .. _cps-version_schema: https://cps-org.github.io/cps/schema.html#version-schema
 .. |cps-version_schema| replace:: ``version_schema``
+
+CPS Transitive Requirements
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A |CPS| package description consists of one or more components which may in
+turn depend on other components either internal or external to the package.
+When external components are required, the providing package is noted as
+a package-level requirement of the package.  Additionally, the set of required
+components is typically noted in said external package requirement.
+
+Where a CMake-script package description would use the
+:command:`find_dependency` command to handle transitive dependencies, CMake
+handles transitive dependencies for CPS itself using an internally nested
+``find_package`` call.  This call can resolve CPS package dependencies via
+*either* another CPS package, or via a CMake-script package.  The manner in
+which the CPS component dependencies are handled is subject to some caveats.
+
+When the candidate for resolving a transitive dependency is another CPS
+package, things are simple; ``COMPONENTS`` and CPS "components" are directly
+comparable (and are effectively synonymous with CMake "imported targets").
+CMake-script packages, however, are encouraged to (and often do) check that
+required components were found, whether or not the package describes separate
+components.  Additionally, even those that do describe components typically do
+not have the same correlation to imported targets that is normal for CPS.  As
+a result, passing the set of required components declared by a CPS package to
+``COMPONENTS`` would result in spurious failures to resolve dependencies.
+
+To address this, if a candidate for resolving a CPS transitive dependency is a
+CMake-script package, CMake passes the required components as declared by the
+consuming CPS package as ``OPTIONAL_COMPONENTS`` and performs a separate,
+internal check that the candidate package supplied the required imported
+targets.  Those targets must be named ``<PackageName>::<ComponentName>``, in
+conformance with CPS convention, or the check will consider the package not
+found.
diff --git a/Modules/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake
index f3e3d1f..9fe3951 100644
--- a/Modules/CMakeDetermineCompilerABI.cmake
+++ b/Modules/CMakeDetermineCompilerABI.cmake
@@ -67,6 +67,8 @@
       # executables.  This may lead to issues when their stderr output (which
       # contains the relevant compiler internals) becomes interweaved.
       string(REGEX REPLACE "(^| )-pipe( |$)" " " ${v} "${${v}}")
+      # Suppress any formatting of warnings and/or errors
+      string(REGEX REPLACE "(-f|/)diagnostics(-|:)color(=[a-z]+)?" "" ${v} "${${v}}")
     endforeach()
 
     # Save the current LC_ALL, LC_MESSAGES, and LANG environment variables
diff --git a/Modules/CMakePrintSystemInformation.cmake b/Modules/CMakePrintSystemInformation.cmake
index 45ad38e..98e48e5 100644
--- a/Modules/CMakePrintSystemInformation.cmake
+++ b/Modules/CMakePrintSystemInformation.cmake
@@ -5,10 +5,26 @@
 CMakePrintSystemInformation
 ---------------------------
 
-Print system information.
+This module can be used for diagnostics to print system information.
 
-This module serves diagnostic purposes. Just include it in a
-project to see various internal CMake variables.
+Examples
+^^^^^^^^
+
+Including this module in a project:
+
+.. code-block:: cmake
+
+  include(CMakePrintSystemInformation)
+
+prints various internal CMake variables.  For example::
+
+  CMAKE_SYSTEM is Linux-6.11.0-17-generic Linux 6.11.0-17-generic x86_64
+  CMAKE_SYSTEM file is Platform/Linux
+  CMAKE_C_COMPILER is /usr/bin/cc
+  CMAKE_CXX_COMPILER is /usr/bin/c++
+  CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS is -shared
+  CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS is -shared
+  ...
 #]=======================================================================]
 
 message("CMAKE_SYSTEM is ${CMAKE_SYSTEM} ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_SYSTEM_PROCESSOR}")
diff --git a/Modules/CheckIPOSupported.cmake b/Modules/CheckIPOSupported.cmake
index 9c4f61e..3db7ae4 100644
--- a/Modules/CheckIPOSupported.cmake
+++ b/Modules/CheckIPOSupported.cmake
@@ -7,9 +7,9 @@
 
 .. versionadded:: 3.9
 
-Check whether the compiler supports an interprocedural optimization (IPO/LTO).
-Use this before enabling the :prop_tgt:`INTERPROCEDURAL_OPTIMIZATION` target
-property.
+This module provides a function to check whether the compiler supports an
+interprocedural optimization (IPO/LTO).  Use this module before enabling the
+:prop_tgt:`INTERPROCEDURAL_OPTIMIZATION` target property.
 
 .. command:: check_ipo_supported
 
@@ -60,21 +60,33 @@
 Examples
 ^^^^^^^^
 
+Checking whether the compiler supports IPO and emitting a fatal error if it is
+not supported:
+
 .. code-block:: cmake
 
+  include(CheckIPOSupported)
   check_ipo_supported() # fatal error if IPO is not supported
   set_property(TARGET foo PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
 
+The following example demonstrates how to use the ``CheckIPOSupported`` module
+to enable IPO for the target only when supported by the compiler and to issue a
+warning if it is not.  Additionally, projects may want to provide a
+configuration option to control when IPO is enabled.  For example:
+
 .. code-block:: cmake
 
-  # Optional IPO. Do not use IPO if it's not supported by compiler.
-  check_ipo_supported(RESULT result OUTPUT output)
-  if(result)
-    set_property(TARGET foo PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
-  else()
-    message(WARNING "IPO is not supported: ${output}")
-  endif()
+  option(FOO_ENABLE_IPO "Enable IPO/LTO")
 
+  if(FOO_ENABLE_IPO)
+    include(CheckIPOSupported)
+    check_ipo_supported(RESULT result OUTPUT output)
+    if(result)
+      set_property(TARGET foo PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
+    else()
+      message(WARNING "IPO is not supported: ${output}")
+    endif()
+  endif()
 #]=======================================================================]
 
 # X_RESULT - name of the final result variable
diff --git a/Modules/CheckIncludeFile.cmake b/Modules/CheckIncludeFile.cmake
index f070ba5..a3a2cee 100644
--- a/Modules/CheckIncludeFile.cmake
+++ b/Modules/CheckIncludeFile.cmake
@@ -35,9 +35,24 @@
 
 .. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
-See the :module:`CheckIncludeFiles` module to check for multiple headers
-at once.  See the :module:`CheckIncludeFileCXX` module to check for headers
-using the ``CXX`` language.
+Examples
+^^^^^^^^
+
+Checking whether the ``C`` header ``<unistd.h>`` exists and storing the check
+result in the ``HAVE_UNISTD_H`` cache variable:
+
+.. code-block:: cmake
+
+  include(CheckIncludeFile)
+
+  check_include_file(unistd.h HAVE_UNISTD_H)
+
+See Also
+^^^^^^^^
+
+* The :module:`CheckIncludeFileCXX` module to check for single ``C++`` header.
+* The :module:`CheckIncludeFiles` module to check for one or more ``C`` or
+  ``C++`` headers at once.
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckIncludeFileCXX.cmake b/Modules/CheckIncludeFileCXX.cmake
index ec02b36..756abf1 100644
--- a/Modules/CheckIncludeFileCXX.cmake
+++ b/Modules/CheckIncludeFileCXX.cmake
@@ -35,8 +35,24 @@
 
 .. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
-See modules :module:`CheckIncludeFile` and :module:`CheckIncludeFiles`
-to check for one or more ``C`` headers.
+Examples
+^^^^^^^^
+
+Checking whether the ``C++23`` header ``<stdfloat>`` exists and storing the
+check result in the ``HAVE_STDFLOAT_HEADER`` cache variable:
+
+.. code-block:: cmake
+
+  include(CheckIncludeFileCXX)
+
+  check_include_file_cxx(stdfloat HAVE_STDFLOAT_HEADER)
+
+See Also
+^^^^^^^^
+
+* The :module:`CheckIncludeFile` module to check for single ``C`` header.
+* The :module:`CheckIncludeFiles` module to check for one or more ``C`` or
+  ``C++`` headers at once.
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckIncludeFiles.cmake b/Modules/CheckIncludeFiles.cmake
index 6dfb41d..855d450 100644
--- a/Modules/CheckIncludeFiles.cmake
+++ b/Modules/CheckIncludeFiles.cmake
@@ -41,8 +41,41 @@
 
 .. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
-See modules :module:`CheckIncludeFile` and :module:`CheckIncludeFileCXX`
-to check for a single header file in ``C`` or ``CXX`` languages.
+Examples
+^^^^^^^^
+
+Checking whether one or more ``C`` headers exist and storing the check result
+in cache variables:
+
+.. code-block:: cmake
+
+  include(CheckIncludeFiles)
+
+  check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
+
+  if(HAVE_SYS_SOCKET_H)
+    # The <net/if.h> header on Darwin and BSD-like systems is not self-contained
+    # and also requires <sys/socket.h>
+    check_include_files("sys/socket.h;net/if.h" HAVE_NET_IF_H)
+  else()
+    check_include_files(net/if.h HAVE_NET_IF_H)
+  endif()
+
+The ``LANGUAGE`` option can be used to specify which compiler to use.  For
+example, checking multiple ``C++`` headers, when both ``C`` and ``CXX``
+languages are enabled in the project:
+
+.. code-block:: cmake
+
+  include(CheckIncludeFiles)
+
+  check_include_files("header_1.hpp;header_2.hpp" HAVE_HEADERS LANGUAGE CXX)
+
+See Also
+^^^^^^^^
+
+* The :module:`CheckIncludeFile` module to check for single ``C`` header.
+* The :module:`CheckIncludeFileCXX` module to check for single ``C++`` header.
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckPIESupported.cmake b/Modules/CheckPIESupported.cmake
index 9922aec..c0cc3c0 100644
--- a/Modules/CheckPIESupported.cmake
+++ b/Modules/CheckPIESupported.cmake
@@ -7,10 +7,16 @@
 
 .. versionadded:: 3.14
 
-Check whether the linker supports Position Independent Code (PIE) or No
-Position Independent Code (NO_PIE) for executables.
-Use this to ensure that the :prop_tgt:`POSITION_INDEPENDENT_CODE` target
-property for executables will be honored at link time.
+This module provides the ``check_pie_supported()`` function to check whether the
+linker supports Position Independent Code (PIE) or No Position Independent Code
+(NO_PIE) for executables.
+
+When setting the :prop_tgt:`POSITION_INDEPENDENT_CODE` target property,
+PIC-related compile and link options are added when building library objects,
+and PIE-related compile options are added when building objects of executable
+targets, regardless of this module.  Use this module to ensure that the
+``POSITION_INDEPENDENT_CODE`` target property for executables is also honored at
+link time.
 
 .. command:: check_pie_supported
 
@@ -36,14 +42,16 @@
 
       ``OBJC``, ``OBJCXX``, ``CUDA``, and ``HIP`` are supported.
 
-It makes no sense to use this module when :policy:`CMP0083` is set to ``OLD``,
-so the command will return an error in this case.  See policy :policy:`CMP0083`
-for details.
+  .. note::
+
+    To use ``check_pie_supported()``, policy :policy:`CMP0083` must be set to
+    ``NEW``; otherwise, a fatal error will occur.
 
 Variables
 ^^^^^^^^^
 
-For each language checked, two boolean cache variables are defined.
+For each language checked, the ``check_pie_supported()`` function defines two
+boolean cache variables:
 
  ``CMAKE_<lang>_LINK_PIE_SUPPORTED``
    Set to true if ``PIE`` is supported by the linker and false otherwise.
@@ -53,21 +61,44 @@
 Examples
 ^^^^^^^^
 
+To enable PIE on an executable target at link time as well, include this module
+and call ``check_pie_supported()`` before setting the
+``POSITION_INDEPENDENT_CODE`` target property.  This will determine whether the
+linker for each checked language supports PIE-related link options.  For
+example:
+
 .. code-block:: cmake
 
+  add_executable(foo ...)
+
+  include(CheckPIESupported)
   check_pie_supported()
   set_property(TARGET foo PROPERTY POSITION_INDEPENDENT_CODE TRUE)
 
+Since not all linkers require or support PIE-related link options (for example,
+``MSVC``), retrieving any error messages might be useful for logging purposes:
+
 .. code-block:: cmake
 
-  # Retrieve any error message.
+  add_executable(foo ...)
+
+  include(CheckPIESupported)
   check_pie_supported(OUTPUT_VARIABLE output LANGUAGES C)
   set_property(TARGET foo PROPERTY POSITION_INDEPENDENT_CODE TRUE)
   if(NOT CMAKE_C_LINK_PIE_SUPPORTED)
-    message(WARNING "PIE is not supported at link time: ${output}.\n"
+    message(WARNING "PIE is not supported at link time:\n${output}"
                     "PIE link options will not be passed to linker.")
   endif()
 
+Setting the ``POSITION_INDEPENDENT_CODE`` target property on an executable
+without this module will set PIE-related compile options but not PIE-related
+link options, which might not be sufficient in certain cases:
+
+.. code-block:: cmake
+
+  add_executable(foo ...)
+  set_property(TARGET foo PROPERTY POSITION_INDEPENDENT_CODE TRUE)
+
 #]=======================================================================]
 
 
diff --git a/Modules/FindHDF5.cmake b/Modules/FindHDF5.cmake
index 2f0c3c1..89058ca 100644
--- a/Modules/FindHDF5.cmake
+++ b/Modules/FindHDF5.cmake
@@ -218,6 +218,13 @@
   set(HDF5_Fortran_COMPILER_NAMES h5fc h5pfc)
 endif()
 
+# Prefer h5hl<LANG> compilers if HDF5_FIND_HL is enabled
+if(HDF5_FIND_HL)
+  list(PREPEND HDF5_C_COMPILER_NAMES h5hlcc)
+  list(PREPEND HDF5_CXX_COMPILER_NAMES h5hlc++)
+  list(PREPEND HDF5_Fortran_COMPILER_NAMES h5hlfc)
+endif()
+
 # Test first if the current compilers automatically wrap HDF5
 function(_HDF5_test_regular_compiler_C success version is_parallel)
   if(NOT ${success} OR
diff --git a/Modules/FindSQLite3.cmake b/Modules/FindSQLite3.cmake
index fcacbef..60eb541 100644
--- a/Modules/FindSQLite3.cmake
+++ b/Modules/FindSQLite3.cmake
@@ -66,7 +66,7 @@
 
 include(FindPackageHandleStandardArgs)
 find_package_handle_standard_args(SQLite3
-    REQUIRED_VARS SQLite3_INCLUDE_DIR SQLite3_LIBRARY
+    REQUIRED_VARS SQLite3_LIBRARY SQLite3_INCLUDE_DIR
     VERSION_VAR SQLite3_VERSION)
 
 # Create the imported target
diff --git a/Modules/Use_wxWindows.cmake b/Modules/Use_wxWindows.cmake
index 5f46784..e3c1870 100644
--- a/Modules/Use_wxWindows.cmake
+++ b/Modules/Use_wxWindows.cmake
@@ -7,39 +7,41 @@
 
 .. deprecated:: 2.8.10
 
-  Use ``find_package(wxWidgets)`` and ``include(${wxWidgets_USE_FILE})`` instead.
+  Use :module:`find_package(wxWidgets) <FindwxWidgets>` instead.
 
-This convenience include finds if wxWindows is installed and set the
-appropriate libs, incdirs, flags etc.  author Jan Woetzel <jw -at-
-mip.informatik.uni-kiel.de> (07/2003)
+This convenience include finds if wxWindows library is installed and sets the
+appropriate libraries, include directories, flags, etc.
 
-USAGE:
+Examples
+^^^^^^^^
 
-::
+Include ``Use_wxWindows`` module in project's ``CMakeLists.txt``:
 
-   just include Use_wxWindows.cmake
-   in your projects CMakeLists.txt
+.. code-block:: cmake
 
-include( ${CMAKE_MODULE_PATH}/Use_wxWindows.cmake)
+  # CMakeLists.txt
+  include(Use_wxWindows)
 
-::
+When the GL support is required, set ``WXWINDOWS_USE_GL`` *before* including
+this module:
 
-   if you are sure you need GL then
+.. code-block:: cmake
 
-set(WXWINDOWS_USE_GL 1)
-
-::
-
-   *before* you include this file.
+  set(WXWINDOWS_USE_GL ON)
+  include(Use_wxWindows)
 #]=======================================================================]
 
+# Author: Jan Woetzel <jw -at- mip.informatik.uni-kiel.de> (07/2003)
+
 # -----------------------------------------------------
 # 16.Feb.2004: changed INCLUDE to FIND_PACKAGE to read from users own non-system CMAKE_MODULE_PATH (Jan Woetzel JW)
 # 07/2006: rewrite as FindwxWidgets.cmake, kept for backward compatibility JW
 
-message(STATUS "Use_wxWindows.cmake is DEPRECATED. \n"
-"Please use find_package(wxWidgets) and include(${wxWidgets_USE_FILE}) instead. (JW)")
-
+message(
+  DEPRECATION
+  "Use_wxWindows module is DEPRECATED.\n"
+  "Please use find_package(wxWidgets) instead. (JW)"
+)
 
 # ------------------------
 
diff --git a/Modules/UsewxWidgets.cmake b/Modules/UsewxWidgets.cmake
index 7152afe..3751025 100644
--- a/Modules/UsewxWidgets.cmake
+++ b/Modules/UsewxWidgets.cmake
@@ -5,39 +5,46 @@
 UsewxWidgets
 ------------
 
-Convenience include for using wxWidgets library.
+This module calls :command:`include_directories` and
+:command:`link_directories`, sets compile definitions for the current directory
+and appends some compile flags to use wxWidgets library after calling the
+:module:`find_package(wxWidgets) <FindwxWidgets>`.
 
-Determines if wxWidgets was FOUND and sets the appropriate libs,
-incdirs, flags, etc.  INCLUDE_DIRECTORIES and LINK_DIRECTORIES are
-called.
+Examples
+^^^^^^^^
 
-USAGE
+Include ``UsewxWidgets`` module in project's ``CMakeLists.txt``:
 
 .. code-block:: cmake
 
-  # Note that for MinGW users the order of libs is important!
+  # Note that for MinGW users the order of libraries is important.
   find_package(wxWidgets REQUIRED net gl core base)
+
+  # Above also sets the wxWidgets_USE_FILE variable that points to this module.
   include(${wxWidgets_USE_FILE})
-  # and for each of your dependent executable/library targets:
-  target_link_libraries(<YourTarget> ${wxWidgets_LIBRARIES})
 
+  # Link wxWidgets libraries for each dependent executable/library target.
+  target_link_libraries(<ProjectTarget> ${wxWidgets_LIBRARIES})
 
+As of CMake 3.27, a better approach is to link only the
+:module:`wxWidgets::wxWidgets <FindwxWidgets>` ``IMPORTED`` target to specific
+targets that require it, rather than including this module. Imported targets
+provide better control of the package usage properties, such as include
+directories and compile flags, by applying them only to the targets they are
+linked to, avoiding unnecessary propagation to all targets in the current
+directory.
 
-DEPRECATED
+.. code-block:: cmake
 
-::
+  # CMakeLists.txt
+  find_package(wxWidgets)
 
-  LINK_LIBRARIES is not called in favor of adding dependencies per target.
-
-
-
-AUTHOR
-
-::
-
-  Jan Woetzel <jw -at- mip.informatik.uni-kiel.de>
+  # Link the imported target for each dependent executable/library target.
+  target_link_libraries(<ProjectTarget> wxWidgets::wxWidgets)
 #]=======================================================================]
 
+# Author: Jan Woetzel <jw -at- mip.informatik.uni-kiel.de>
+
 if   (wxWidgets_FOUND)
   if   (wxWidgets_INCLUDE_DIRS)
     if(wxWidgets_INCLUDE_DIRS_NO_SYSTEM)
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 30dfb3b..45be758 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 4)
 set(CMake_VERSION_MINOR 0)
-set(CMake_VERSION_PATCH 20250308)
+set(CMake_VERSION_PATCH 20250311)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/cmCurl.cxx b/Source/cmCurl.cxx
index 1b8c765..4d8d6a5 100644
--- a/Source/cmCurl.cxx
+++ b/Source/cmCurl.cxx
@@ -179,7 +179,7 @@
                                  std::string const& netrc_file)
 {
   std::string e;
-  CURL_NETRC_OPTION curl_netrc_level = CURL_NETRC_LAST;
+  long curl_netrc_level = CURL_NETRC_LAST;
   ::CURLcode res;
 
   if (!netrc_level.empty()) {
diff --git a/Source/cmPathResolver.cxx b/Source/cmPathResolver.cxx
index 71ce7b4..32f3ef7 100644
--- a/Source/cmPathResolver.cxx
+++ b/Source/cmPathResolver.cxx
@@ -492,6 +492,14 @@
   static constexpr Options::Symlinks Symlinks = Options::Symlinks::None;
   static constexpr Options::Existence Existence = Options::Existence::Agnostic;
 };
+struct CasePath
+{
+#if defined(_WIN32) || defined(__APPLE__)
+  static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
+#endif
+  static constexpr Options::Symlinks Symlinks = Options::Symlinks::None;
+  static constexpr Options::Existence Existence = Options::Existence::Agnostic;
+};
 struct RealPath
 {
 #if defined(_WIN32) || defined(__APPLE__)
@@ -512,6 +520,8 @@
 #if defined(__SUNPRO_CC)
 constexpr Options::Symlinks NaivePath::Symlinks;
 constexpr Options::Existence NaivePath::Existence;
+constexpr Options::Symlinks CasePath::Symlinks;
+constexpr Options::Existence CasePath::Existence;
 constexpr Options::Symlinks RealPath::Symlinks;
 constexpr Options::Existence RealPath::Existence;
 constexpr Options::Symlinks LogicalPath::Symlinks;
@@ -535,6 +545,7 @@
 
 template class Resolver<Policies::LogicalPath>;
 template class Resolver<Policies::RealPath>;
+template class Resolver<Policies::CasePath>;
 template class Resolver<Policies::NaivePath>;
 
 }
diff --git a/Source/cmPathResolver.h b/Source/cmPathResolver.h
index 1f96e0a..e2399ba 100644
--- a/Source/cmPathResolver.h
+++ b/Source/cmPathResolver.h
@@ -78,13 +78,18 @@
 
 /** Normalizes paths while resolving symlinks only when followed
     by '..' components.  Does not require paths to exist, but
-    reads on-disk case of paths that do exist (on Windows).  */
+    reads on-disk case of paths that do exist (on Windows and macOS).  */
 struct LogicalPath;
 
-/** Normalizes paths while resolving all symlinks.
-    Requires paths to exist, and reads their on-disk case (on Windows).  */
+/** Normalizes paths while resolving all symlinks.  Requires paths to exist,
+    and reads their on-disk case (on Windows and macOS).  */
 struct RealPath;
 
+/** Normalizes paths while assuming components followed by '..'
+    components are not symlinks.  Does not require paths to exist, but
+    reads on-disk case of paths that do exist (on Windows and macOS).  */
+struct CasePath;
+
 /** Normalizes paths in memory without disk access.
     Assumes components followed by '..' components are not symlinks.  */
 struct NaivePath;
@@ -94,6 +99,7 @@
 
 extern template class Resolver<Policies::LogicalPath>;
 extern template class Resolver<Policies::RealPath>;
+extern template class Resolver<Policies::CasePath>;
 extern template class Resolver<Policies::NaivePath>;
 
 }
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 45e59ec..0755148 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -1305,23 +1305,77 @@
   return filepath_.empty();
 }
 
+std::string cmSystemTools::GetRealPathResolvingWindowsSubst(
+  std::string const& path, std::string* errorMessage)
+{
+#ifdef _WIN32
+  // uv_fs_realpath uses Windows Vista API so fallback to kwsys if not found
+  std::string resolved_path;
+  uv_fs_t req;
+  int err = uv_fs_realpath(nullptr, &req, path.c_str(), nullptr);
+  if (!err) {
+    resolved_path = std::string((char*)req.ptr);
+    cmSystemTools::ConvertToUnixSlashes(resolved_path);
+  } else if (err == UV_ENOSYS) {
+    resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage);
+  } else if (errorMessage) {
+    cmsys::Status status =
+      cmsys::Status::Windows(uv_fs_get_system_error(&req));
+    *errorMessage = status.GetString();
+    resolved_path.clear();
+  } else {
+    resolved_path = path;
+  }
+  // Normalize to upper-case drive letter as cm::PathResolver does.
+  if (resolved_path.size() > 1 && resolved_path[1] == ':') {
+    resolved_path[0] = toupper(resolved_path[0]);
+  }
+  return resolved_path;
+#else
+  return cmsys::SystemTools::GetRealPath(path, errorMessage);
+#endif
+}
+
 std::string cmSystemTools::GetRealPath(std::string const& path,
                                        std::string* errorMessage)
 {
 #ifdef _WIN32
-  std::string resolved_path;
-  using namespace cm::PathResolver;
-  // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath
-  static Resolver<Policies::RealPath> const resolver(RealOS);
-  cmsys::Status status = resolver.Resolve(path, resolved_path);
-  if (!status) {
-    if (errorMessage) {
-      *errorMessage = status.GetString();
-      resolved_path.clear();
-    } else {
-      resolved_path = path;
+  std::string resolved_path =
+    cmSystemTools::GetRealPathResolvingWindowsSubst(path, errorMessage);
+
+  // If the original path used a subst drive and the real path starts
+  // with the substitution, restore the subst drive prefix.  This may
+  // incorrectly restore a subst drive if the underlying drive was
+  // encountered via an absolute symlink, but this is an acceptable
+  // limitation to otherwise preserve susbt drives.
+  if (resolved_path.size() >= 2 && resolved_path[1] == ':' &&
+      path.size() >= 2 && path[1] == ':' &&
+      toupper(resolved_path[0]) != toupper(path[0])) {
+    // FIXME: Add thread_local or mutex if we use threads.
+    static std::map<char, std::string> substMap;
+    char const drive = static_cast<char>(toupper(path[0]));
+    std::string maybe_subst = cmStrCat(drive, ":/");
+    auto smi = substMap.find(drive);
+    if (smi == substMap.end()) {
+      smi = substMap
+              .emplace(
+                drive,
+                cmSystemTools::GetRealPathResolvingWindowsSubst(maybe_subst))
+              .first;
+    }
+    std::string const& resolved_subst = smi->second;
+    std::string::size_type const ns = resolved_subst.size();
+    if (ns > 0) {
+      std::string::size_type const np = resolved_path.size();
+      if (ns == np && resolved_path == resolved_subst) {
+        resolved_path = maybe_subst;
+      } else if (ns > 0 && ns < np && resolved_path[ns] == '/' &&
+                 resolved_path.compare(0, ns, resolved_subst) == 0) {
+        resolved_path.replace(0, ns + 1, maybe_subst);
+      }
     }
   }
+
   return resolved_path;
 #else
   return cmsys::SystemTools::GetRealPath(path, errorMessage);
@@ -1991,8 +2045,13 @@
 std::string cmSystemTools::ToNormalizedPathOnDisk(std::string p)
 {
   using namespace cm::PathResolver;
+#ifdef _WIN32
+  // IWYU pragma: no_forward_declare cm::PathResolver::Policies::CasePath
+  static Resolver<Policies::CasePath> const resolver(RealOS);
+#else
   // IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath
   static Resolver<Policies::LogicalPath> const resolver(RealOS);
+#endif
   resolver.Resolve(std::move(p), p);
   return p;
 }
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index f7ac20b..16c5caa 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -441,9 +441,11 @@
   /** Convert an input path to an absolute path with no '/..' components.
       Backslashes in the input path are converted to forward slashes.
       Relative paths are interpreted w.r.t. GetLogicalWorkingDirectory.
-      On Windows, the on-disk capitalization is loaded for existing paths.
       This is similar to 'realpath', but preserves symlinks that are
-      not erased by '../' components.  */
+      not erased by '../' components.
+
+      On Windows and macOS, the on-disk capitalization is loaded for
+      existing paths.  */
   static std::string ToNormalizedPathOnDisk(std::string p);
 
 #ifndef CMAKE_BOOTSTRAP
@@ -649,6 +651,12 @@
   static std::string GetComspec();
 #endif
 
+  /** Get the real path for a given path, removing all symlinks.
+      This variant of GetRealPath also works on Windows but will
+      resolve subst drives too.  */
+  static std::string GetRealPathResolvingWindowsSubst(
+    std::string const& path, std::string* errorMessage = nullptr);
+
   /** Get the real path for a given path, removing all symlinks.  */
   static std::string GetRealPath(std::string const& path,
                                  std::string* errorMessage = nullptr);
diff --git a/Source/cmTimestamp.cxx b/Source/cmTimestamp.cxx
index d921a89..35913c7 100644
--- a/Source/cmTimestamp.cxx
+++ b/Source/cmTimestamp.cxx
@@ -63,7 +63,8 @@
                                               std::string const& formatString,
                                               bool utcFlag) const
 {
-  std::string real_path = cmSystemTools::GetRealPath(path);
+  std::string real_path =
+    cmSystemTools::GetRealPathResolvingWindowsSubst(path);
 
   if (!cmsys::SystemTools::FileExists(real_path)) {
     return std::string();
diff --git a/Tests/CMakeLib/testPathResolver.cxx b/Tests/CMakeLib/testPathResolver.cxx
index d79afd9..d5cee74 100644
--- a/Tests/CMakeLib/testPathResolver.cxx
+++ b/Tests/CMakeLib/testPathResolver.cxx
@@ -24,6 +24,7 @@
 
 // IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath
 // IWYU pragma: no_forward_declare cm::PathResolver::Policies::NaivePath
+// IWYU pragma: no_forward_declare cm::PathResolver::Policies::CasePath
 // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath
 
 namespace {
@@ -213,6 +214,22 @@
   });
 
   {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("/link-a", "/link-a");
+    EXPECT_RESOLVE("/link-a-excess", "/link-a-excess");
+    EXPECT_RESOLVE("/link-a-excess/b", "/link-a-excess/b");
+    EXPECT_RESOLVE("/link-broken", "/link-broken");
+    EXPECT_RESOLVE("/link-a/../missing", "/missing");
+    EXPECT_RESOLVE("/a/b/link-c", "/a/b/link-c");
+    EXPECT_RESOLVE("/a/link-b/c", "/a/link-b/c");
+    EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/link-b");
+    EXPECT_RESOLVE("/a/b/c/link-..|..", "/a/b/c/link-..|..");
+    EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b/c/link-..|../link-b");
+    EXPECT_RESOLVE("/a/link-|1|2/3", "/a/link-|1|2/3");
+    EXPECT_RESOLVE("/a/link-|1|2/../2/3", "/a/2/3");
+  }
+
+  {
     Resolver<Policies::LogicalPath> const r(os);
     EXPECT_RESOLVE("/link-a", "/link-a");
     EXPECT_RESOLVE("/link-a-excess", "/link-a-excess");
@@ -264,6 +281,16 @@
   });
 
   {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG");
+    EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/LiNk-MiXeD");
+    EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD/LiNk-C-MiXeD");
+    EXPECT_RESOLVE("/upper/mIsSiNg", "/UPPER/mIsSiNg");
+    EXPECT_RESOLVE("/upper/link-upper", "/UPPER/LINK-UPPER");
+    EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER/LINK-C-UPPER");
+  }
+
+  {
     Resolver<Policies::LogicalPath> const r(os);
     EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG");
     EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/LiNk-MiXeD");
@@ -302,6 +329,16 @@
     EXPECT_RESOLVE("C:/..", "C:/");
     EXPECT_RESOLVE("c:/../", "c:/");
   }
+  {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("c:/", "C:/");
+    EXPECT_RESOLVE("C:/", "C:/");
+    EXPECT_RESOLVE("c://", "C:/");
+    EXPECT_RESOLVE("C:/.", "C:/");
+    EXPECT_RESOLVE("c:/./", "C:/");
+    EXPECT_RESOLVE("C:/..", "C:/");
+    EXPECT_RESOLVE("c:/../", "C:/");
+  }
   os.SetPaths({
     { "c:/", { {}, {} } },
     { "//host/", { {}, {} } },
@@ -361,6 +398,16 @@
   });
 
   {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG");
+    EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD");
+    EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD/LiNk-C-MiXeD");
+    EXPECT_RESOLVE("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg");
+    EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/LINK-UPPER");
+    EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER/LINK-C-UPPER");
+  }
+
+  {
     Resolver<Policies::LogicalPath> const r(os);
     EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG");
     EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD");
@@ -441,6 +488,27 @@
     EXPECT_RESOLVE("E:.", "E:/");
     EXPECT_RESOLVE("E:..", "E:/");
   }
+  {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("c:", "C:/cwd");
+    EXPECT_RESOLVE("c:.", "C:/cwd");
+    EXPECT_RESOLVE("c:..", "C:/");
+    EXPECT_RESOLVE("C:", "C:/cwd");
+    EXPECT_RESOLVE("C:.", "C:/cwd");
+    EXPECT_RESOLVE("C:..", "C:/");
+    EXPECT_RESOLVE("d:", "D:/cwd-d");
+    EXPECT_RESOLVE("d:.", "D:/cwd-d");
+    EXPECT_RESOLVE("d:..", "D:/");
+    EXPECT_RESOLVE("D:", "D:/cwd-d");
+    EXPECT_RESOLVE("D:.", "D:/cwd-d");
+    EXPECT_RESOLVE("D:..", "D:/");
+    EXPECT_RESOLVE("e:", "E:/");
+    EXPECT_RESOLVE("e:.", "E:/");
+    EXPECT_RESOLVE("e:..", "E:/");
+    EXPECT_RESOLVE("E:", "E:/");
+    EXPECT_RESOLVE("E:.", "E:/");
+    EXPECT_RESOLVE("E:..", "E:/");
+  }
   os.SetPaths({
     { "c:/", { {}, {} } },
     { "c:/cwd", { {}, {} } },
@@ -496,6 +564,13 @@
     EXPECT_RESOLVE("link-to-host-share/..", "//host/");
     EXPECT_RESOLVE("link-to-host-share/../missing", "//host/missing");
   }
+
+  {
+    Resolver<Policies::CasePath> const r(os);
+    EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share");
+    EXPECT_RESOLVE("link-to-host-share/..", "C:/cwd");
+    EXPECT_RESOLVE("link-to-host-share/../missing", "C:/cwd/missing");
+  }
   return true;
 }
 #endif
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/CheckCompilerLinkerId.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/CheckCompilerLinkerId.cmake
new file mode 100644
index 0000000..a6a56ca
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/CheckCompilerLinkerId.cmake
@@ -0,0 +1,5 @@
+enable_language(C)
+
+if(NOT CMAKE_C_COMPILER_LINKER OR NOT CMAKE_C_COMPILER_LINKER_ID)
+  message(FATAL_ERROR "Failed to determine Linker.")
+endif()
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
index d50d403..df03f15 100644
--- a/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
@@ -39,3 +39,7 @@
       )
   endif()
 endif()
+
+if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES Clang)
+  run_cmake_with_options(CheckCompilerLinkerId "-DCMAKE_C_FLAGS=-fdiagnostics-color=always")
+endif()